Skip to content

Commit 97e5b15

Browse files
authored
Fix #644 app.message listener does not handle events when a file is attached (#645)
1 parent 20bfec8 commit 97e5b15

File tree

4 files changed

+358
-0
lines changed

4 files changed

+358
-0
lines changed

slack_bolt/app/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,9 @@ def __call__(*args, **kwargs):
837837
# If an end-user posts a message with "Also send to #channel" checked,
838838
# the message event comes with this subtype.
839839
"thread_broadcast",
840+
# If an end-user posts a message with attached files,
841+
# the message event comes with this subtype.
842+
"file_share",
840843
),
841844
}
842845
primary_matcher = builtin_matchers.message_event(

slack_bolt/app/async_app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,9 @@ def __call__(*args, **kwargs):
894894
# If an end-user posts a message with "Also send to #channel" checked,
895895
# the message event comes with this subtype.
896896
"thread_broadcast",
897+
# If an end-user posts a message with attached files,
898+
# the message event comes with this subtype.
899+
"file_share",
897900
),
898901
}
899902
primary_matcher = builtin_matchers.message_event(
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import json
2+
import time
3+
4+
from slack_sdk.signature import SignatureVerifier
5+
from slack_sdk.web import WebClient
6+
7+
from slack_bolt.app import App
8+
from slack_bolt.request import BoltRequest
9+
from tests.mock_web_api_server import (
10+
setup_mock_web_api_server,
11+
cleanup_mock_web_api_server,
12+
assert_auth_test_count,
13+
)
14+
from tests.utils import remove_os_env_temporarily, restore_os_env
15+
16+
17+
class TestMessageFileShare:
18+
signing_secret = "secret"
19+
valid_token = "xoxb-valid"
20+
mock_api_server_base_url = "http://localhost:8888"
21+
signature_verifier = SignatureVerifier(signing_secret)
22+
web_client = WebClient(
23+
token=valid_token,
24+
base_url=mock_api_server_base_url,
25+
)
26+
27+
def setup_method(self):
28+
self.old_os_env = remove_os_env_temporarily()
29+
setup_mock_web_api_server(self)
30+
31+
def teardown_method(self):
32+
cleanup_mock_web_api_server(self)
33+
restore_os_env(self.old_os_env)
34+
35+
def generate_signature(self, body: str, timestamp: str):
36+
return self.signature_verifier.generate_signature(
37+
body=body,
38+
timestamp=timestamp,
39+
)
40+
41+
def build_headers(self, timestamp: str, body: str):
42+
return {
43+
"content-type": ["application/json"],
44+
"x-slack-signature": [self.generate_signature(body, timestamp)],
45+
"x-slack-request-timestamp": [timestamp],
46+
}
47+
48+
def build_request(self, payload: dict) -> BoltRequest:
49+
timestamp, body = str(int(time.time())), json.dumps(payload)
50+
return BoltRequest(body=body, headers=self.build_headers(timestamp, body))
51+
52+
def test_message_handler(self):
53+
app = App(
54+
client=self.web_client,
55+
signing_secret=self.signing_secret,
56+
)
57+
58+
result = {"call_count": 0}
59+
60+
@app.message("Hi there!")
61+
def handle_messages(event, logger):
62+
logger.info(event)
63+
result["call_count"] = result["call_count"] + 1
64+
65+
request = self.build_request(event_payload)
66+
response = app.dispatch(request)
67+
assert response.status == 200
68+
69+
request = self.build_request(event_payload)
70+
response = app.dispatch(request)
71+
assert response.status == 200
72+
73+
assert_auth_test_count(self, 1)
74+
time.sleep(1) # wait a bit after auto ack()
75+
assert result["call_count"] == 2
76+
77+
78+
event_payload = {
79+
"token": "xxx",
80+
"team_id": "T111",
81+
"api_app_id": "A111",
82+
"event": {
83+
"type": "message",
84+
"text": "Hi there!",
85+
"files": [
86+
{
87+
"id": "F111",
88+
"created": 1652227642,
89+
"timestamp": 1652227642,
90+
"name": "file.png",
91+
"title": "file.png",
92+
"mimetype": "image/png",
93+
"filetype": "png",
94+
"pretty_type": "PNG",
95+
"user": "U111",
96+
"editable": False,
97+
"size": 92582,
98+
"mode": "hosted",
99+
"is_external": False,
100+
"external_type": "",
101+
"is_public": True,
102+
"public_url_shared": False,
103+
"display_as_bot": False,
104+
"username": "",
105+
"url_private": "https://files.slack.com/files-pri/T111-F111/file.png",
106+
"url_private_download": "https://files.slack.com/files-pri/T111-F111/download/file.png",
107+
"media_display_type": "unknown",
108+
"thumb_64": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_64.png",
109+
"thumb_80": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_80.png",
110+
"thumb_360": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_360.png",
111+
"thumb_360_w": 360,
112+
"thumb_360_h": 115,
113+
"thumb_480": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_480.png",
114+
"thumb_480_w": 480,
115+
"thumb_480_h": 153,
116+
"thumb_160": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_160.png",
117+
"thumb_720": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_720.png",
118+
"thumb_720_w": 720,
119+
"thumb_720_h": 230,
120+
"thumb_800": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_800.png",
121+
"thumb_800_w": 800,
122+
"thumb_800_h": 255,
123+
"thumb_960": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_960.png",
124+
"thumb_960_w": 960,
125+
"thumb_960_h": 306,
126+
"thumb_1024": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_1024.png",
127+
"thumb_1024_w": 1024,
128+
"thumb_1024_h": 327,
129+
"original_w": 1134,
130+
"original_h": 362,
131+
"thumb_tiny": "AwAPADCkCAOcUEj0zTaKAHZHpT9oxwR+VRVMBQA0r3yPypu0f3v0p5yBTCcmmI//2Q==",
132+
"permalink": "https://xxx.slack.com/files/U111/F111/file.png",
133+
"permalink_public": "https://slack-files.com/T111-F111-faecabecf7",
134+
"has_rich_preview": False,
135+
}
136+
],
137+
"upload": False,
138+
"user": "U111",
139+
"display_as_bot": False,
140+
"ts": "1652227646.593159",
141+
"blocks": [
142+
{
143+
"type": "rich_text",
144+
"block_id": "ba4",
145+
"elements": [
146+
{
147+
"type": "rich_text_section",
148+
"elements": [{"type": "text", "text": "Hi there!"}],
149+
}
150+
],
151+
}
152+
],
153+
"client_msg_id": "ca088267-717f-41a8-9db8-c98ae14ad6a0",
154+
"channel": "C111",
155+
"subtype": "file_share",
156+
"event_ts": "1652227646.593159",
157+
"channel_type": "channel",
158+
},
159+
"type": "event_callback",
160+
"event_id": "Ev03EGJQAVMM",
161+
"event_time": 1652227646,
162+
"authorizations": [
163+
{
164+
"enterprise_id": None,
165+
"team_id": "T111",
166+
"user_id": "U222",
167+
"is_bot": True,
168+
"is_enterprise_install": False,
169+
}
170+
],
171+
"is_ext_shared_channel": False,
172+
"event_context": "4-xxx",
173+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import asyncio
2+
import json
3+
from time import time
4+
5+
import pytest
6+
from slack_sdk.signature import SignatureVerifier
7+
from slack_sdk.web.async_client import AsyncWebClient
8+
9+
from slack_bolt.app.async_app import AsyncApp
10+
from slack_bolt.request.async_request import AsyncBoltRequest
11+
from tests.mock_web_api_server import (
12+
setup_mock_web_api_server,
13+
cleanup_mock_web_api_server,
14+
assert_auth_test_count_async,
15+
)
16+
from tests.utils import remove_os_env_temporarily, restore_os_env
17+
18+
19+
class TestAsyncMessageFileShare:
20+
signing_secret = "secret"
21+
valid_token = "xoxb-valid"
22+
mock_api_server_base_url = "http://localhost:8888"
23+
signature_verifier = SignatureVerifier(signing_secret)
24+
web_client = AsyncWebClient(
25+
token=valid_token,
26+
base_url=mock_api_server_base_url,
27+
)
28+
29+
@pytest.fixture
30+
def event_loop(self):
31+
old_os_env = remove_os_env_temporarily()
32+
try:
33+
setup_mock_web_api_server(self)
34+
loop = asyncio.get_event_loop()
35+
yield loop
36+
loop.close()
37+
cleanup_mock_web_api_server(self)
38+
finally:
39+
restore_os_env(old_os_env)
40+
41+
def generate_signature(self, body: str, timestamp: str):
42+
return self.signature_verifier.generate_signature(
43+
body=body,
44+
timestamp=timestamp,
45+
)
46+
47+
def build_headers(self, timestamp: str, body: str):
48+
return {
49+
"content-type": ["application/json"],
50+
"x-slack-signature": [self.generate_signature(body, timestamp)],
51+
"x-slack-request-timestamp": [timestamp],
52+
}
53+
54+
def build_request(self, payload: dict) -> AsyncBoltRequest:
55+
timestamp, body = str(int(time())), json.dumps(payload)
56+
return AsyncBoltRequest(body=body, headers=self.build_headers(timestamp, body))
57+
58+
@pytest.mark.asyncio
59+
async def test_string_keyword(self):
60+
app = AsyncApp(
61+
client=self.web_client,
62+
signing_secret=self.signing_secret,
63+
)
64+
result = {"call_count": 0}
65+
66+
@app.message("Hi there!")
67+
async def handle_messages(event, logger):
68+
logger.info(event)
69+
result["call_count"] = result["call_count"] + 1
70+
71+
request = self.build_request(event_payload)
72+
response = await app.async_dispatch(request)
73+
assert response.status == 200
74+
75+
request = self.build_request(event_payload)
76+
response = await app.async_dispatch(request)
77+
assert response.status == 200
78+
79+
await assert_auth_test_count_async(self, 1)
80+
await asyncio.sleep(1) # wait a bit after auto ack()
81+
assert result["call_count"] == 2
82+
83+
84+
event_payload = {
85+
"token": "xxx",
86+
"team_id": "T111",
87+
"api_app_id": "A111",
88+
"event": {
89+
"type": "message",
90+
"text": "Hi there!",
91+
"files": [
92+
{
93+
"id": "F111",
94+
"created": 1652227642,
95+
"timestamp": 1652227642,
96+
"name": "file.png",
97+
"title": "file.png",
98+
"mimetype": "image/png",
99+
"filetype": "png",
100+
"pretty_type": "PNG",
101+
"user": "U111",
102+
"editable": False,
103+
"size": 92582,
104+
"mode": "hosted",
105+
"is_external": False,
106+
"external_type": "",
107+
"is_public": True,
108+
"public_url_shared": False,
109+
"display_as_bot": False,
110+
"username": "",
111+
"url_private": "https://files.slack.com/files-pri/T111-F111/file.png",
112+
"url_private_download": "https://files.slack.com/files-pri/T111-F111/download/file.png",
113+
"media_display_type": "unknown",
114+
"thumb_64": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_64.png",
115+
"thumb_80": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_80.png",
116+
"thumb_360": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_360.png",
117+
"thumb_360_w": 360,
118+
"thumb_360_h": 115,
119+
"thumb_480": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_480.png",
120+
"thumb_480_w": 480,
121+
"thumb_480_h": 153,
122+
"thumb_160": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_160.png",
123+
"thumb_720": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_720.png",
124+
"thumb_720_w": 720,
125+
"thumb_720_h": 230,
126+
"thumb_800": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_800.png",
127+
"thumb_800_w": 800,
128+
"thumb_800_h": 255,
129+
"thumb_960": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_960.png",
130+
"thumb_960_w": 960,
131+
"thumb_960_h": 306,
132+
"thumb_1024": "https://files.slack.com/files-tmb/T111-F111-f820f29515/file_1024.png",
133+
"thumb_1024_w": 1024,
134+
"thumb_1024_h": 327,
135+
"original_w": 1134,
136+
"original_h": 362,
137+
"thumb_tiny": "AwAPADCkCAOcUEj0zTaKAHZHpT9oxwR+VRVMBQA0r3yPypu0f3v0p5yBTCcmmI//2Q==",
138+
"permalink": "https://xxx.slack.com/files/U111/F111/file.png",
139+
"permalink_public": "https://slack-files.com/T111-F111-faecabecf7",
140+
"has_rich_preview": False,
141+
}
142+
],
143+
"upload": False,
144+
"user": "U111",
145+
"display_as_bot": False,
146+
"ts": "1652227646.593159",
147+
"blocks": [
148+
{
149+
"type": "rich_text",
150+
"block_id": "ba4",
151+
"elements": [
152+
{
153+
"type": "rich_text_section",
154+
"elements": [{"type": "text", "text": "Hi there!"}],
155+
}
156+
],
157+
}
158+
],
159+
"client_msg_id": "ca088267-717f-41a8-9db8-c98ae14ad6a0",
160+
"channel": "C111",
161+
"subtype": "file_share",
162+
"event_ts": "1652227646.593159",
163+
"channel_type": "channel",
164+
},
165+
"type": "event_callback",
166+
"event_id": "Ev03EGJQAVMM",
167+
"event_time": 1652227646,
168+
"authorizations": [
169+
{
170+
"enterprise_id": None,
171+
"team_id": "T111",
172+
"user_id": "U222",
173+
"is_bot": True,
174+
"is_enterprise_install": False,
175+
}
176+
],
177+
"is_ext_shared_channel": False,
178+
"event_context": "4-xxx",
179+
}

0 commit comments

Comments
 (0)