Skip to content

Commit

Permalink
Add remote function support
Browse files Browse the repository at this point in the history
  • Loading branch information
seratch committed Nov 21, 2023
1 parent ea080c0 commit 191f08c
Show file tree
Hide file tree
Showing 38 changed files with 1,239 additions and 16 deletions.
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

0 comments on commit 191f08c

Please sign in to comment.