Skip to content

Commit 6c76639

Browse files
committed
Fix #769 A new option to auto-acknowledge Events API requests
1 parent 99b8a7f commit 6c76639

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

bolt/src/main/java/com/slack/api/bolt/App.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,20 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi
10671067
EventsApiPayload<Event> payload = buildEventPayload(request);
10681068
return handler.apply(payload, request.getContext());
10691069
}
1070+
if (config().isAllEventsApiAutoAckEnabled()) {
1071+
// If the flag is true, Bolt acknowledges all the events anyway
1072+
// This behavior is compatible with bolt-js.
1073+
log.debug("{} is auto-acknowledged as AppConfig#isAllEventsApiAutoAckEnabled() is true",
1074+
request.getEventTypeAndSubtype());
1075+
return request.getContext().ack();
1076+
}
1077+
boolean isSubtypedMessageEvents = request.getEventTypeAndSubtype() != null
1078+
&& request.getEventTypeAndSubtype().startsWith(MessageEvent.TYPE_NAME + ":");
1079+
if (config().isSubtypedMessageEventsAutoAckEnabled() && isSubtypedMessageEvents) {
1080+
log.debug("{} is auto-acknowledged as AppConfig#isSubtypedMessageEventsAutoAckEnabled() is true",
1081+
request.getEventTypeAndSubtype());
1082+
return request.getContext().ack();
1083+
}
10701084
log.warn("No BoltEventHandler registered for event: {}\n{}",
10711085
request.getEventTypeAndSubtype(), ListenerCodeSuggestion.event(request.getEventTypeAndSubtype()));
10721086
break;

bolt/src/main/java/com/slack/api/bolt/AppConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,20 @@ public void setOauthRedirectUriPath(String oauthRedirectUriPath) {
352352
@Builder.Default
353353
private boolean appInitializersEnabled = true;
354354

355+
/**
356+
* Automatically acknowledge message events that have subtype if true.
357+
* Find the list of available subtypes at https://api.slack.com/events/message
358+
*/
359+
@Builder.Default
360+
private boolean subtypedMessageEventsAutoAckEnabled = false;
361+
362+
/**
363+
* Automatically acknowledge all Event API events if true.
364+
* This behavior is compatible with bolt-js.
365+
*/
366+
@Builder.Default
367+
private boolean allEventsApiAutoAckEnabled = false;
368+
355369
// ---------------------------------
356370
// Default middleware configuration
357371
// ---------------------------------

bolt/src/test/java/test_locally/app/EventTest.java

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,118 @@ public void bot() throws Exception {
159159
assertTrue(botMessageReceived.get());
160160
}
161161

162+
@Test
163+
public void allEventsAutoAck() throws Exception {
164+
App app = buildApp();
165+
app.config().setAllEventsApiAutoAckEnabled(true);
166+
167+
{
168+
// human message
169+
EventsApiPayload<MessageEvent> payload = buildUserMessagePayload();
170+
171+
String requestBody = gson.toJson(payload);
172+
Map<String, List<String>> rawHeaders = new HashMap<>();
173+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
174+
setRequestHeaders(requestBody, rawHeaders, timestamp);
175+
176+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
177+
Response response = app.run(req);
178+
// Although no listener is registered in App, this request must be acknowledged.
179+
assertEquals(200L, response.getStatusCode().longValue());
180+
}
181+
{
182+
// bot
183+
EventsApiPayload<MessageBotEvent> payload = buildUserBotMessagePayload();
184+
String requestBody = gson.toJson(payload);
185+
Map<String, List<String>> rawHeaders = new HashMap<>();
186+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
187+
setRequestHeaders(requestBody, rawHeaders, timestamp);
188+
189+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
190+
Response response = app.run(req);
191+
192+
// Although no listener is registered in App, this request must be acknowledged.
193+
assertEquals(200L, response.getStatusCode().longValue());
194+
}
195+
{
196+
// bot handled by listener
197+
AtomicBoolean called = new AtomicBoolean(false);
198+
app.event(MessageBotEvent.class, (req, ctx) -> {
199+
called.set(true);
200+
return ctx.ack();
201+
});
202+
203+
EventsApiPayload<MessageBotEvent> payload = buildUserBotMessagePayload();
204+
String requestBody = gson.toJson(payload);
205+
Map<String, List<String>> rawHeaders = new HashMap<>();
206+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
207+
setRequestHeaders(requestBody, rawHeaders, timestamp);
208+
209+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
210+
Response response = app.run(req);
211+
212+
// Although no listener is registered in App, this request must be acknowledged.
213+
assertEquals(200L, response.getStatusCode().longValue());
214+
assertTrue(called.get());
215+
}
216+
}
217+
218+
@Test
219+
public void subtypedMessageEventsAutoAck() throws Exception {
220+
App app = buildApp();
221+
app.config().setSubtypedMessageEventsAutoAckEnabled(true);
222+
223+
{
224+
// human message
225+
EventsApiPayload<MessageEvent> payload = buildUserMessagePayload();
226+
227+
String requestBody = gson.toJson(payload);
228+
Map<String, List<String>> rawHeaders = new HashMap<>();
229+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
230+
setRequestHeaders(requestBody, rawHeaders, timestamp);
231+
232+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
233+
Response response = app.run(req);
234+
// non-subtyped one should be handled by listeners.
235+
assertEquals(404L, response.getStatusCode().longValue());
236+
}
237+
{
238+
// bot
239+
EventsApiPayload<MessageBotEvent> payload = buildUserBotMessagePayload();
240+
String requestBody = gson.toJson(payload);
241+
Map<String, List<String>> rawHeaders = new HashMap<>();
242+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
243+
setRequestHeaders(requestBody, rawHeaders, timestamp);
244+
245+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
246+
Response response = app.run(req);
247+
248+
// Although no listener is registered in App, this request must be acknowledged.
249+
assertEquals(200L, response.getStatusCode().longValue());
250+
}
251+
{
252+
// bot handled by listener
253+
AtomicBoolean called = new AtomicBoolean(false);
254+
app.event(MessageBotEvent.class, (req, ctx) -> {
255+
called.set(true);
256+
return ctx.ack();
257+
});
258+
259+
EventsApiPayload<MessageBotEvent> payload = buildUserBotMessagePayload();
260+
String requestBody = gson.toJson(payload);
261+
Map<String, List<String>> rawHeaders = new HashMap<>();
262+
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
263+
setRequestHeaders(requestBody, rawHeaders, timestamp);
264+
265+
EventRequest req = new EventRequest(requestBody, new RequestHeaders(rawHeaders));
266+
Response response = app.run(req);
267+
268+
// Although no listener is registered in App, this request must be acknowledged.
269+
assertEquals(200L, response.getStatusCode().longValue());
270+
assertTrue(called.get());
271+
}
272+
}
273+
162274
App buildApp() {
163275
return new App(AppConfig.builder()
164276
.signingSecret(secret)

0 commit comments

Comments
 (0)