Improving Operational Work with a Slack BotSave time and automate repetitive tasksDanilo Assis
Danilo Assis
@daniloab
@daniloab_
Tech Lead - Fullstack Developer

Overview

why
slack bolt
libs: js, python, java
how to create a new app
how to create a new app from manifest yml
how to set oauth tokens and permissions
slack bot commands
slack API
slack API sdk methods
timeout
git
cron
what else?

Why

  • mainly communication channel
  • operational worker
  • daily routines
  • make easier our life

Workflow Builder

Workflow Builder

Slack Bolt

  • The Bolt family of SDKs
  • Bolt is the swiftest way to start programming with the Slack Platform in JavaScript, Python, or Java.
  • Install your app using OAuth 2.0
  • Receive real time events and messages with the Events API
  • Compose rich, interactive messages with Block Kit
  • Respond to slash commands, shortcuts, and interactive messages
  • Use a wide library of Web API methods

Bolt Libs

Bolt Js

  • NodeJS SDK
  • Slack Web API
  • Events API
  • Webhooks
  • https://slack.dev/bolt-js/tutorial/getting-started

Creating new App

  • Manually
  • Manifest

Creating new App

  • Manually

Creating new App

  • creating

Creating new App

  • filling it

Creating new App

  • filled

Creating new App

  • created

Creating new App - Manifest

  • yml
  • faster
  • plug n play

Slack App yml

display_information:
name: Botina BOT
features:
app_home:
home_tab_enabled: false
messages_tab_enabled: true
messages_tab_read_only_enabled: false
bot_user:
display_name: Botina
always_online: true
oauth_config:
scopes:
bot:
- channels:history
- channels:join
- channels:read
- chat:write
- commands
- im:history
- users:read
settings:
event_subscriptions:
bot_events:
- message.channels
- message.im
interactivity:
is_enabled: true
org_deploy_enabled: false
socket_mode_enabled: true
token_rotation_enabled: false

manifest yml

Slack Commands

app.message("Lembrete", async ({ message, say }) => {
// code here
});

Slack Message obj

{
message: {
client_msg_id: 'd478217c-4cce-4771-8df8-4c8332edde96',
type: 'message',
text: 'Lembrete',
user: 'U03NHSA09LY',
ts: '1658323599.100439',
team: 'T03MWG732FN',
blocks: [
{
type: 'rich_text',
block_id: 'rpTZ',
elements: [
{
type: 'rich_text_section',
elements: [ { type: 'text', text: 'Lembrete' } ]
}
]
}
],
channel: 'C03MMFU7PAS',
event_ts: '1658323599.100439',
channel_type: 'channel'
}
}

Slack Say

await say({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Diff ${latestReleaseTag}*`,
},
},
{
type: "divider",
},
...getCommitsBlocks(),
],
});

Slack SDK

  • https://api.slack.com/methods

Slack SDK

  • Docs

Slack SDK

const result = await app.client.chat.postMessage({
token: "xoxb-3744551104532-3793906652642-7eqtTUIaeP5i2M1rjs0U9jiJ",
channel: 'channel_id',
thread_ts: 'thread_id',
text: 'what you want to say',
});
return result;

Slack API

  • https://api.slack.com/docs

Slack API

  • Docs

Slack Block Kit Builder

  • Block Kit Builder

Examples

  • timeout
  • cron
  • daily thread
  • diff

Timeout

import {conversationMessagePost} from "../../botina-slack/src/conversationMessagePost";
app.message("timeout", async ({ message, say }) => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @Danilo Assis, this is a timeout message"
);
}, 5000);
});

Cron

import {conversationMessagePost} from "../../botina-slack/src/conversationMessagePost";
app.message("cron", async ({ message, say }) => {
cron.schedule("* * * * *", () => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @Danilo Assis, this is a cron message every minute"
);
}, 5000);
});
});

Daily Thread

import {conversationMessagePost} from "../../botina-slack/src/conversationMessagePost";
app.message("Reminder: Daily thread.", async ({ message, say }) => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @daniloassis, check if the team answer the thread"
);
}, 60000);
});

Daily Thread Challenges

  • get users id
  • check if each one answered the thread
  • remind
  • blocks

Diff

app.message("diff", async ({ message, say }) => {
if (!process.env.GITHUB_TOKEN) {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"You do not have the github token configured"
);
return;
}
await getDiff(say, process.env.OWNER, process.env.REPO);
});
// get diff
export const getDiff = async (
say: SayFn,
owner = "daniloab",
repo = "botina-slack"
) => {
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN,
});
const latestReleases = await octokit.rest.repos.listReleases({
owner,
repo,
per_page: 1,
});
const latestReleaseTag =
latestReleases && latestReleases.data && latestReleases.data.length
? latestReleases.data[0].tag_name
: "main";
const response = await octokit.rest.repos.compareCommits({
owner,
repo,
base: latestReleaseTag,
head: "main",
});
const getCommitsBlocks = () => {
let blocks = [];
for (const commit of response.data.commits) {
blocks = [
...blocks,
{
type: "section",
text: {
type: "mrkdwn",
text: commit.commit.message,
},
},
{
type: "divider",
},
];
}
return blocks;
};
await say({
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Diff ${latestReleaseTag}*`,
},
},
{
type: "divider",
},
...getCommitsBlocks(),
],
});
};

diff message

Diff Challenges

  • improve dx
  • improve blocks
  • release button

Contributing

  • clone the repo
  • fill with tokens
  • install in your slack app

Tokens

  • to develop based in this bot you will need 3 tokens
  • signing token
  • app token
  • bot token

Signing Token

  • get it on basic information
  • go to App Credentials
  • In Signing Secret click on show and copy the token

Signing Token

App Token

  • start with xoxb
  • got to https://api.slack.com/apps/
  • Select your ap then on sidebar go to OAuth & Permissions

App Token

Bot Token

  • start with xapp
  • generated manually
  • App-level tokens allow your app to use platform features that apply to multiple (or all) installations
  • App-level tokens are used to authenticate your app to the platform
  • In basic information section go to App-Level Tokens
  • Create a new app level token to your bot and copy the value

Bot Token

Bot Token

Bot Token

Final Result

require("isomorphic-fetch");
import { App } from "@slack/bolt";
import cron from "node-cron";
import { conversationReplyGet } from "./conversationReplyGet";
import { conversationMessagePost } from "./conversationMessagePost";
import { userGet } from "./userGet";
import dotenvSafe from "dotenv-safe";
import path from "path";
import { getDiff } from "./git/getDiff";
const root = path.join.bind(this, __dirname, "../");
dotenvSafe.config({
path: root(".env"),
sample: root(".env.example"),
});
const app = new App({
token: process.env.TOKEN,
appToken: process.env.APP_TOKEN,
signingSecret: process.env.SIGNING_SECRET_TOKEN,
socketMode: true,
});
const generalChannelId = "C03MMFU7PAS";
const myUserId = "U03NHSA09LY";
app.message("Lembrete", async ({ message, say }) => {
await conversationMessagePost(
app,
generalChannelId,
message?.thread_ts,
"Hey @Danilo Assis, answer the thread"
);
});
app.message("Reminder: Daily thread.", async ({ message, say }) => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @daniloassis, check if the team answer the thread"
);
}, 60000);
});
app.message("find", async ({ message, say }) => {
await conversationReplyGet(app, generalChannelId, message.event_ts);
});
app.message("timeout", async ({ message, say }) => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @Danilo Assis, this is a timeout message"
);
}, 5000);
});
app.message("user", async ({ message }) => {
await userGet(app, myUserId);
});
app.message("cron", async ({ message, say }) => {
cron.schedule("* * * * *", () => {
setTimeout(async () => {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"Hey @Danilo Assis, this is a cron message every minute"
);
}, 5000);
});
});
app.message("diff", async ({ message, say }) => {
if (!process.env.GITHUB_TOKEN) {
await conversationMessagePost(
app,
generalChannelId,
message?.event_ts,
"You do not have the github token configured"
);
return;
}
await getDiff(say, process.env.OWNER, process.env.REPO);
});
(async () => {
await app.start(process.env.PORT || 3000);
console.log("⚡️ BOTina app is running!");
})();

diff message

References

  • https://slack.dev/
  • https://slack.dev/bolt-js/tutorial/getting-started
  • https://api.slack.com/authentication/basics
  • https://slack.dev/node-slack-sdk/
  • https://app.slack.com/block-kit-builder/
Be my Patreon:
https://www.patreon.com/daniloab
Social Medias
https://linktr.ee/daniloab
Give me a Feedback:
https://entria.feedback.house/danilo
Thanks!
We are hiring!
Join Us