Skip to content

Instantly share code, notes, and snippets.

@seratch
Created February 18, 2021 15:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save seratch/447309ebb5c680887017a27d0c28d7e1 to your computer and use it in GitHub Desktop.
Save seratch/447309ebb5c680887017a27d0c28d7e1 to your computer and use it in GitHub Desktop.
Slack App in Google Apps Script
// *** Request Verification ***
// The currently recommended way is to verify request signature: https://api.slack.com/authentication/verifying-requests-from-slack
// Unfortunately, GAS web apps don"t have means to access request headers. Thus, this example uses the old way to verify requests.
// >>> Settings > Basic Information > App Credentials > Verification Token
const legacyVerificationToken = PropertiesService.getScriptProperties().getProperty("SLACK_VERIFICATION_TOKEN");
// *** OAuth Access Token ***
// Install your Slack app into its development workspace.
// >>> Settings > Install App > Bot User OAuth Access Token
const token = PropertiesService.getScriptProperties().getProperty("SLACK_BOT_TOKEN");
// Example modal view
// Build your own modals here: https://app.slack.com/block-kit-builder
const modalView = {
"type": "modal",
"callback_id": "modal-id",
"submit": {
"type": "plain_text",
"text": "Submit"
},
"close": {
"type": "plain_text",
"text": "Cancel"
},
"title": {
"type": "plain_text",
"text": "Notification settings"
},
"blocks": [
{
"type": "actions",
"block_id": "b",
"elements": [
{
"type": "checkboxes",
"action_id": "a",
"options": [
{
"text": {
"type": "plain_text",
"text": "New tasks"
},
"value": "tasks",
"description": {
"type": "plain_text",
"text": "When new tasks are added to project"
}
},
{
"text": {
"type": "plain_text",
"text": "New comments"
},
"value": "comments",
"description": {
"type": "plain_text",
"text": "When new comments are added"
}
},
{
"text": {
"type": "plain_text",
"text": "Project updates"
},
"value": "updates",
"description": {
"type": "plain_text",
"text": "When project is updated"
}
}
]
}
]
}
]
};
// POST request handler
function doPost(e) {
console.log(`Incoming form request data: ${JSON.stringify(e.postData)}`);
if (typeof e.postData === "undefined") {
return ack("invalid request");
}
if (e.postData.type === "application/json") {
// ----------------------------
// Events API / url_verification
// ----------------------------
const payload = JSON.parse(e.postData.getDataAsString());
if (payload.token !== legacyVerificationToken) {
console.log(`Invalid verification token detected (actual: ${payload.token}, expected: ${legacyVerificationToken})`);
return ack("invalid request");
}
if (typeof payload.challenge !== "undefined") {
return ack(payload.challenge);
}
console.log(`Events API payload: ${JSON.stringify(payload)}`);
// -------------------------------------------------------------
// TODO: Implement your own logic here
if (typeof payload.event.channel !== "undefined") {
callWebApi(token, "chat.postMessage", {
channel: payload.event.channel,
text: "Hi there!"
});
}
// -------------------------------------------------------------
return ack("");
} else if (e.postData.type === "application/x-www-form-urlencoded") {
if (typeof e.parameters.payload !== "undefined") {
// ----------------------------
// Interactivity & Shortcuts
// ----------------------------
const payload = JSON.parse(e.parameters.payload[0]);
if (payload.token !== legacyVerificationToken) {
console.log(`Invalid verification token detected (actual: ${payload.token}, expected: ${legacyVerificationToken})`);
return ack("invalid request");
}
console.log(`Interactivity payload: ${JSON.stringify(payload)}`);
// -------------------------------------------------------------
// TODO: Implement your own logic here
if (payload.type === "shortcut") {
if (payload.callback_id === "gas") {
callWebApi(token, "views.open", {
trigger_id: payload.trigger_id,
user_id: payload.user.id,
view: JSON.stringify(modalView)
});
}
} else if (payload.type === "message_action") {
if (payload.callback_id === "gas-msg") {
respond(payload.response_url, "Thanks for running a message shortcut!");
}
} else if (payload.type === "block_actions") {
console.log(`Action data: ${JSON.stringify(payload.actions[0])}`);
} else if (payload.type === "view_submission") {
if (payload.view.callback_id === "modal-id") {
const stateValues = payload.view.state.values;
console.log(`View submssion data: ${JSON.stringify(stateValues)}`);
return ack("");
}
}
// -------------------------------------------------------------
} else if (typeof e.parameters.command !== "undefined") {
// ----------------------------
// Slash Commands
// ----------------------------
const payload = {}
for (const [key, value] of Object.entries(e.parameters)) {
payload[key] = value[0];
}
if (payload.token !== legacyVerificationToken) {
console.log(`Invalid verification token detected (actual: ${payload.token}, expected: ${legacyVerificationToken})`);
return ack("invalid request");
}
console.log(`Slash command payload: ${JSON.stringify(payload)}`);
// -------------------------------------------------------------
// TODO: Implement your own logic here
if (payload.command === "/gas") {
return ack("Hi there!");
}
// -------------------------------------------------------------
}
}
// acknowledge the request
return ack("");
}
function callWebApi(token, apiMethod, payload) {
const response = UrlFetchApp.fetch(
`https://www.slack.com/api/${apiMethod}`,
{
method: "post",
contentType: "application/x-www-form-urlencoded",
headers: { "Authorization": `Bearer ${token}` },
payload: payload,
}
);
console.log(`Web API (${apiMethod}) response: ${response}`)
return response;
}
function respond(responseUrl, payload) {
var responseBody = payload;
if (typeof payload === "string") {
responseBody = { "text": payload };
}
const response = UrlFetchApp.fetch(responseUrl, {
method: "post",
contentType: "application/json; charset=utf-8",
payload: JSON.stringify(responseBody),
}
);
console.log(response);
}
function ack(payload) {
if (typeof payload === "string") {
return ContentService.createTextOutput(payload);
} else {
return ContentService.createTextOutput(JSON.stringify(payload));
}
}
@michaelconan
Copy link

michaelconan commented Nov 11, 2022

@seratch thank you for this great resource.
It would be great if you could make one update that I identified in my project and is required for the use of the response_action method of clearing, updating, pushing modals or showing error messages:

function ack(payload) {
  if (typeof payload === "string") {
    return ContentService.createTextOutput(payload);
  } else {
    return ContentService.createTextOutput(JSON.stringify(payload))
               .setMimeType(ContentService.MimeType.JSON);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment