Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 91 additions & 4 deletions bolt-socket-mode/src/test/java/samples/SimpleApp.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package samples;

import com.google.gson.Gson;
import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.model.Message;
import com.slack.api.model.block.element.RichTextSectionElement;
import com.slack.api.model.event.AppMentionEvent;
import com.slack.api.model.event.MessageChangedEvent;
import com.slack.api.model.event.MessageDeletedEvent;
import com.slack.api.model.event.MessageEvent;
import com.slack.api.model.event.*;
import com.slack.api.model.view.ViewState;
import com.slack.api.util.json.GsonFactory;
import config.Constants;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.dispatchActionConfig;
Expand Down Expand Up @@ -153,6 +153,93 @@ public static void main(String[] args) throws Exception {
return ctx.ack();
});

// app.event(FunctionExecutedEvent.class, (req, ctx) -> {
// app.function("hello", (req, ctx) -> {
app.function(Pattern.compile("^he.+$"), (req, ctx) -> {
ctx.logger.info("req: {}", req);
ctx.client().chatPostMessage(r -> r
.channel(req.getEvent().getInputs().get("user_id").asString())
.text("hey!")
.blocks(asBlocks(actions(a -> a.blockId("b").elements(asElements(
button(b -> b.actionId("remote-function-button-success").value("clicked").text(plainText("block_actions success"))),
button(b -> b.actionId("remote-function-button-error").value("clicked").text(plainText("block_actions error"))),
button(b -> b.actionId("remote-function-modal").value("clicked").text(plainText("modal view")))
)))))
);
return ctx.ack();
});

app.blockAction("remote-function-button-success", (req, ctx) -> {
Map<String, Object> outputs = new HashMap<>();
outputs.put("user_id", req.getPayload().getFunctionData().getInputs().get("user_id").asString());
ctx.client().functionsCompleteSuccess(r -> r
.functionExecutionId(req.getPayload().getFunctionData().getExecutionId())
.outputs(outputs)
);
ctx.client().chatUpdate(r -> r
.channel(req.getPayload().getContainer().getChannelId())
.ts(req.getPayload().getContainer().getMessageTs())
.text("Thank you!")
);
return ctx.ack();
});
app.blockAction("remote-function-button-error", (req, ctx) -> {
ctx.client().functionsCompleteError(r -> r
.functionExecutionId(req.getPayload().getFunctionData().getExecutionId())
.error("test error!")
);
ctx.client().chatUpdate(r -> r
.channel(req.getPayload().getContainer().getChannelId())
.ts(req.getPayload().getContainer().getMessageTs())
.text("Thank you!")
);
return ctx.ack();
});
app.blockAction("remote-function-modal", (req, ctx) -> {
ctx.client().viewsOpen(r -> r
.triggerId(req.getPayload().getInteractivity().getInteractivityPointer())
.view(view(v -> v
.type("modal")
.callbackId("remote-function-view")
.title(viewTitle(vt -> vt.type("plain_text").text("Remote Function test")))
.close(viewClose(vc -> vc.type("plain_text").text("Close")))
.submit(viewSubmit(vs -> vs.type("plain_text").text("Submit")))
.notifyOnClose(true)
.blocks(asBlocks(input(input -> input
.blockId("text-block")
.element(plainTextInput(pti -> pti.actionId("text-action").multiline(true)))
.label(plainText(pt -> pt.text("Text").emoji(true)))
)))
)));
ctx.client().chatUpdate(r -> r
.channel(req.getPayload().getContainer().getChannelId())
.ts(req.getPayload().getContainer().getMessageTs())
.text("Thank you!")
);
return ctx.ack();
});

app.viewSubmission("remote-function-view", (req, ctx) -> {
Map<String, Object> outputs = new HashMap<>();
outputs.put("user_id", ctx.getRequestUserId());
ctx.client().functionsCompleteSuccess(r -> r
.token(req.getPayload().getBotAccessToken())
.functionExecutionId(req.getPayload().getFunctionData().getExecutionId())
.outputs(outputs)
);
return ctx.ack();
});
app.viewClosed("remote-function-view", (req, ctx) -> {
Map<String, Object> outputs = new HashMap<>();
outputs.put("user_id", ctx.getRequestUserId());
ctx.client().functionsCompleteSuccess(r -> r
.token(req.getPayload().getBotAccessToken())
.functionExecutionId(req.getPayload().getFunctionData().getExecutionId())
.outputs(outputs)
);
return ctx.ack();
});

String appToken = System.getenv(Constants.SLACK_SDK_TEST_SOCKET_MODE_APP_TOKEN);
SocketModeApp socketModeApp = new SocketModeApp(appToken, app);
socketModeApp.start();
Expand Down
33 changes: 29 additions & 4 deletions bolt/src/main/java/com/slack/api/bolt/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.response.auth.AuthTestResponse;
import com.slack.api.model.event.AppUninstalledEvent;
import com.slack.api.model.event.Event;
import com.slack.api.model.event.MessageEvent;
import com.slack.api.model.event.TokensRevokedEvent;
import com.slack.api.model.event.*;
import com.slack.api.util.json.GsonFactory;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -582,6 +579,7 @@ public Response run(Request request) throws Exception {
if (request == null || request.getContext() == null) {
return Response.builder().statusCode(400).body("Invalid Request").build();
}
request.getContext().setAttachingFunctionTokenEnabled(this.config().isAttachingFunctionTokenEnabled());
request.getContext().setSlack(slack()); // use the properly configured API client

if (neverStarted.get()) {
Expand Down Expand Up @@ -648,6 +646,33 @@ public App event(EventHandler<?> handler) {
return this;
}

public App function(String callbackId, BoltEventHandler<FunctionExecutedEvent> handler) {
return event(FunctionExecutedEvent.class, (req, ctx) -> {
if (log.isDebugEnabled()) {
log.debug("Run a function_executed event handler (callback_id: {})", callbackId);
}
if (callbackId.equals(req.getEvent().getFunction().getCallbackId())) {
return handler.apply(req, ctx);
} else {
return null;
}
});
}

public App function(Pattern callbackId, BoltEventHandler<FunctionExecutedEvent> handler) {
return event(FunctionExecutedEvent.class, (req, ctx) -> {
if (log.isDebugEnabled()) {
log.debug("Run a function_executed event handler (callback_id: {})", callbackId);
}
String sentCallbackId = req.getEvent().getFunction().getCallbackId();
if (callbackId.matcher(sentCallbackId).matches()) {
return handler.apply(req, ctx);
} else {
return null;
}
});
}

public App message(String pattern, BoltEventHandler<MessageEvent> messageHandler) {
return message(Pattern.compile("^.*" + Pattern.quote(pattern) + ".*$"), messageHandler);
}
Expand Down
9 changes: 9 additions & 0 deletions bolt/src/main/java/com/slack/api/bolt/AppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,15 @@ public void setOauthRedirectUriPath(String oauthRedirectUriPath) {
@Builder.Default
private boolean allEventsApiAutoAckEnabled = false;

/**
* When true, the framework automatically attaches context#functionBotAccessToken
* to context#client instead of context#botToken.
* Enabling this behavior only affects function_executed event handlers
* and app.action/app.view handlers associated with the function token.
*/
@Builder.Default
private boolean attachingFunctionTokenEnabled = true;

// ---------------------------------
// Default middleware configuration
// ---------------------------------
Expand Down
23 changes: 21 additions & 2 deletions bolt/src/main/java/com/slack/api/bolt/context/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ public abstract class Context {
* A bot token associated with this request. The format must be starting with `xoxb-`.
*/
protected String botToken;

/**
* When true, the framework automatically attaches context#functionBotAccessToken
* to context#client instead of context#botToken.
* Enabling this behavior only affects function_executed event handlers
* and app.action/app.view handlers associated with the function token.
*/
private boolean attachingFunctionTokenEnabled;

/**
* The bot token associated with this "function_executed"-type event and its interactions.
* The format must be starting with `xoxb-`.
*/
protected String functionBotAccessToken;

/**
* The scopes associated to the botToken
*/
Expand Down Expand Up @@ -88,17 +103,21 @@ public abstract class Context {
protected final Map<String, String> additionalValues = new HashMap<>();

public MethodsClient client() {
String primaryToken = (isAttachingFunctionTokenEnabled() && functionBotAccessToken != null)
? functionBotAccessToken : botToken;
// We used to pass teamId only for org-wide installations, but we changed this behavior since version 1.10.
// The reasons are 1) having teamId in the MethodsClient can reduce TeamIdCache's auth.test API calls
// 2) OpenID Connect + token rotation allows only refresh token to perform auth.test API calls.
return getSlack().methods(botToken, teamId);
return getSlack().methods(primaryToken, teamId);
}

public AsyncMethodsClient asyncClient() {
String primaryToken = (isAttachingFunctionTokenEnabled() && functionBotAccessToken != null)
? functionBotAccessToken : botToken;
// We used to pass teamId only for org-wide installations, but we changed this behavior since version 1.10.
// The reasons are 1) having teamId in the MethodsClient can reduce TeamIdCache's auth.test API calls
// 2) OpenID Connect + token rotation allows only refresh token to perform auth.test API calls.
return getSlack().methodsAsync(botToken, teamId);
return getSlack().methodsAsync(primaryToken, teamId);
}

public ChatPostMessageResponse say(BuilderConfigurator<ChatPostMessageRequest.ChatPostMessageRequestBuilder> request) throws IOException, SlackApiException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@ public BlockActionRequest(
this.headers = headers;
this.payload = GsonFactory.createSnakeCase().fromJson(payloadBody, BlockActionPayload.class);
if (this.payload != null) {
getContext().setFunctionBotAccessToken(payload.getBotAccessToken());
getContext().setResponseUrl(payload.getResponseUrl());
getContext().setTriggerId(payload.getTriggerId());
if (payload.getTriggerId() == null
&& payload.getInteractivity() != null
&& payload.getInteractivity().getInteractivityPointer() != null) {
getContext().setTriggerId(payload.getInteractivity().getInteractivityPointer());
}
if (payload.getEnterprise() != null) {
getContext().setEnterpriseId(payload.getEnterprise().getId());
} else if (payload.getTeam() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.slack.api.bolt.request.Request;
import com.slack.api.bolt.request.RequestHeaders;
import com.slack.api.bolt.request.RequestType;
import com.slack.api.model.event.FunctionExecutedEvent;
import com.slack.api.util.json.GsonFactory;
import lombok.ToString;

Expand Down Expand Up @@ -104,6 +105,13 @@ public EventRequest(
} else if (event.get("channel_id") != null) {
this.getContext().setChannelId(event.get("channel_id").getAsString());
}

if (this.eventType != null
&& this.eventType.equals(FunctionExecutedEvent.TYPE_NAME)
&& event.get("bot_access_token") != null) {
String functionBotAccessToken = event.get("bot_access_token").getAsString();
this.getContext().setFunctionBotAccessToken(functionBotAccessToken);
}
}

private EventContext context = new EventContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public ViewClosedRequest(
getContext().setTeamId(payload.getUser().getTeamId());
}
getContext().setRequestUserId(payload.getUser().getId());
getContext().setFunctionBotAccessToken(payload.getBotAccessToken());
}

private DefaultContext context = new DefaultContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public ViewSubmissionRequest(
}
getContext().setRequestUserId(payload.getUser().getId());
getContext().setResponseUrls(payload.getResponseUrls());
getContext().setFunctionBotAccessToken(payload.getBotAccessToken());
}

private ViewSubmissionContext context = new ViewSubmissionContext();
Expand Down
Loading