Automate the Boring Stuff with Slackbot (ver. 2)

Takanori Suzuki

PyCon JP 2022 / 2022 Oct 15

退屈なことは Slackbot にやらせよう (ver. 2)

Takanori Suzuki

PyCon JP 2022 / 2022 Oct 15

Agenda / アジェンダ 📋

  • Background and Motivation for Slackbot

  • How to create simple bot

  • How to create interactive bot

  • How to extend bot using libs and APIs

Photos 📷 Tweets 🐦 👍

#pyconjp / #pyconjp_1 / @takanory

Slide 💻

Why ver. 2 in the title? ✌️

なぜタイトルに ver. 2 が入ってるの?

Back to 2019 / 2019年に遡る ⏪

  • Title: “Automate the Boring Stuff with Slackbot”

  • Talk in 🇵🇭 🇹🇭 🇲🇾 🇯🇵 🇹🇼 🇸🇬 🇮🇩


And the 2022 / そして2022年 ⏩

  • Updated with latest information 🆕

  • In-person event after COVID-19 in Japan 🇯🇵

  • Thanks to PyCon JP staff and volunteers!! 👏

Who am I? / お前誰よ 👤

../_images/sokidan-square.jpg ../_images/kurokuri.jpg

BeProud inc. 🏢

  • BeProud: Pythonシステム開発、Consulting

  • connpass: IT勉強会支援プラットフォーム

  • PyQ: Python独学プラットフォーム

  • TRACERY: システム開発ドキュメントサービス


BeProud Booth


AD is over / 宣伝は終了

Background and Motivation 💪


Conference Tasks

カンファレンスの タスク

  • I held PyCon JP(2014-2016) as Chair

  • Conference tasks:

    • 👨‍💻 Keynotes, Talks and Trainings

    • 🎫 Ticket sales and reception

    • 🏬 Venue and facility(WiFi, Video…)

    • 🍱 Foods, ☕️ Coffee, 🧁 Snacks and 🍺 Beers

Staff ask me the same things

スタッフは 同じこと を質問する

  • 40+ staff

  • 🐣 NEW staff : 🐔 OLD staff = 50 : 50

Programmer is Lazy

プログラマーは 怠惰

Let’s create a secretary!!

秘書 を作ろう!!

Goal / ゴール 🥅

  • How to create simple bot

  • How to create interactive bot

  • How to extend bot using libs and APIs

Why Slack ? / なぜ Slack? 💬

  • Launching the Slack app at any time 💻 📱

  • Easy to access

  • To do everything


You can create interactive bot

対話 botが作れるようになる

../_images/bot-result12.png ../_images/bot-result22.png

Simple integration with Incoming Webhooks 🪝

Incoming Webhooks での簡単な連携

System overview / システム概要


Create Incoming Webhooks Integration 🔧

Incoming Webhooks連携を 作成

Create Incoming Webhooks Integration

1. Create a Slack app / Slack appを作成

../_images/create-webhook1-12.png ../_images/create-webhook1-22.png
  • Enter name and choose workspace

  • Set app icon (optional)


2. Activate Incoming Webhooks / 有効化


3. Add Webhook to Workspace / ワークスペースに追加

  • Click “Add New Webhook to Workspace”

  • Choose channel → Click “Allow”

  • Get Webhook URL:


Post message via Webhook URL 📬

Webhook URL 経由でメッセージを投稿

Post message with cURL

$ curl -X POST -H 'Content-type: application/json' \
> --data '{"text":"Hello Slack!"}' \

Post message with Python

import json
from urllib import request

url = ""
message = {"text": "Hello from Python!"}
data = json.dumps(message).encode()
request.urlopen(url, data=data)

Post message with Requests

$ pip install requests
import requests

url = ""
message = {"text": "Hello from Requests!"}
r =, json=message)

Post message with Slack SDK

$ pip install slack-sdk
from slack_sdk.webhook import WebhookClient

url = ""
webhook = WebhookClient(url)
r = webhook.send(text="Hello from Slack SDK!")

Formatting text / テキストを整形

from slack_sdk.webhook import WebhookClient

url = ""
webhook = WebhookClient(url)
# *bold*, <url|text>, :emoji: and etc.
sdk_url = ""
r = webhook.send(
    text=f"*Hello* from <{sdk_url}|Slack SDK>! :beer:")

Block Kit 🧱

Block Kit


Example of Block Kit

blocks = [{
    "type": "section",
    "text": {
         "type": "mrkdwn"
         "text": "*THANK YOU* for coming to my talk !:tada: Please give me *feedback* about this talk :bow:",
    "fields": [
         {"type": "mrkdwn", "text": "*Love*"},
         {"type": "mrkdwn", "text": "*Hobby*"},
         {"type": "plain_text", "text": "Ferrets, :beer:, LEGO"},
         {"type": "plain_text", "text": ":trumpet:, :man_climbing:"},
response = webhook.send(blocks=blocks)

Block Kit Builder


Summary of Incoming Webhooks

まとめ: Incoming Webhooks

  • Easy to post messages from programs 📬

  • Create complex messages with Block Kit 🧱

  • But one-way (program➡️Webhook➡️Slack)

Interactive bot 🤝

対話型 のbot

Connection protocols / 接続方式

Events API over HTTP


Socket Mode


Connection protocols / 接続方式

  • Events API over HTTP

  • Socket Mode 👈

Create bot user 🤖

bot userを 作成

Create bot user

  • Create bot user with Socket Mode

    1. Create a Slack app (same procedure)

    2. Enable Socket Mode

    3. Subscribe bot event

    4. Add Bot Token Scopes

    5. Install App to Workspace

  • Invite bot user to Slack channels

1. Create a Slack app / Slack appを作成

../_images/create-webhook1-12.png ../_images/create-webhook1-22.png
  • Enter name and choose workspace

  • Set app icon (optional)


2. Enable Socket Mode / Socket Mode有効化

  • Select “Socket Mode” → Turn toggle on

  • Enter token name → Click “Generate”

  • Get app-level token: xapp-...


3. Subscribe bot event / イベント登録

  • Select “Event Subscriptions” → Turn toggle on

  • Add “message.channels” to bot events

../_images/create-bot3-2-12.png ../_images/create-bot3-2-22.png

4. Add Bot Token Scopes / スコープ追加

  • Select “OAuth & Permissions”

  • Click “Add on OAuth Scope”

  • Add “chat:write” to Bot Token Scopes

../_images/create-bot92.png ../_images/create-bot102.png

5. Install App to Workspace / アプリをインストール

  • Select “Install App” → Click “Install to Workspace”

  • Switch OAuth screen → Click “Allow” button

  • Get Bot Token: xoxb-...


Invite bot user to channels



Long and Complex !! 🤯

手順が 長い複雑 !!

App Manifest ⚙️

App Manifest

Example of App Manifest

  name: beerbot2
    display_name: beerbot2
    always_online: false
      - channels:history
      - chat:write
      - message.channels
    is_enabled: true
  org_deploy_enabled: false
  socket_mode_enabled: true
  token_rotation_enabled: false

Get App Manifest / App Manifestを取得

  • Select “App Manifest” menu


Create new app with App Manifest

App ManifestでSlack appを作成

  • Select “From an app manifest”

  • Select workspace → Click “Next”

../_images/app-manifest12.png ../_images/app-manifest22.png
  • Enter app manifest YAML

  • Review app summary → Click “Create”

../_images/app-manifest42.png ../_images/app-manifest52.png ../_images/app-manifest62.png
  • Install App to Workspace

  • Generate App-Level Token

../_images/app-manifest82.png ../_images/app-manifest92.png

Short and Reusable !! 🥳

短い再利用 できる!!

Create bot with Bolt ⚡️

Bolt を使ってbotを作成

Bolt for Python

Install Bolt for Python

$ mkdir beerbot
$ cd beerbot
$ python3.10 -m venv env
$ . env/bin/activate
(env) $ pip install slack-bolt

Create a simple bot with Bolt

簡単なbotをBoltで 作成
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ["SLACK_BOT_TOKEN"])

# match any message contains "Hi"
def handle_hi_message(message, say):
    say("Hi!!! I am beerbot! :beer:")

if __name__ == "__main__":
    app_token = os.environ["SLACK_APP_TOKEN"]
    SocketModeHandler(app, app_token).start()

Running bot / botを 実行

# Set 2 tokens in environment variables
(env) $ export SLACK_APP_TOKEN=xapp-...
(env) $ export SLACK_BOT_TOKEN=xoxb-...
(env) $ python
⚡️ Bolt app is running!

I can interact with the bot ! 🎉

botと 会話 できた!


Extend bot 🔧

botを 拡張

@app.message() decolator

# match any message contains "Hi"
def handle_hi_message(message, say):
    say("Hi!!! I am beerbot! :beer:")

# match any message contains "cheers"
def handle_cheers_message(mesasge, say):
    say("Cheers! :beers:")


mention / メンション

def handle_morning_message(message, say):
    """reply morning greetings with mention"""
    user = message["user"]  # get user ID
    say(f"Good morning <@{user}>!")


Use regular expression / 正規表現を使う

import random
import re

@app.message(re.compile(r"choice (.*)"))
def handle_choice(say, context):
    """random choice from words"""
    # get matched text from context["matches"]
    words = context["matches"][0].split()


Use regular expression / 正規表現を使う

def handle_beer_or_tea(say, context):
    """serve a specified number of beers or teas"""
    count = int(context["matches"][0])
    beer_or_tea = context["matches"][1]
    say(f":{beer_or_tea}:" * count)

Block Kit support

@app.message("follow me")
def handle_follow_me(message, say):
    """send follow me message with icon"""
    blocks = [{
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": "*Takanori Suzuki*\nFollow me! <|@takanory>",
        "accessory": {
            "type": "image", "alt_text": "takanory",
            "image_url": "",

Logging / ロギング

import logging

def handle_log(message, say, logger):
    logger.debug(f"message: {message['text']}")
    say("logs exported!")
$ python
⚡️ Bolt app is running! log please write log

Events and Scopes 🔭


Events and Scopes

  • Can only receive events in Bot Events

  • Can only execute APIs allowed by Bot Token Scopes

Current Bot Events and Scopes

  • Events


    message posted to public channels

  • Scopes


    View messages in public channels


    Post message

Current Bot Events and Scopes

  • Cannot read/write messages on private channels


Add Events and Scopes for private channels

  • Select “Event Subscriptions” → Click “Add Bot User Event”

  • Add message.groups event→ Click “Save Changes”


Add Events and Scopes for private channels

  • Select “OAuth & Permissions”

  • groups:history scope is automatically added


Add Events and Scopes for private channels

  • Reinstall app to workspace

../_images/add-events-and-scopes32.png ../_images/add-events-and-scopes42.png

Add Events and Scopes for private channels

  • Bot can read/write messages in private channel


To know user joined a channel


  • Add member_joined_channel event → Reinstall app

# A user joined a public or private channel
def member_joined(event, say):
    """send a welcome message to a new member"""
    user = event["user"]  # get user ID
    say(f"Welcome <@{user}>! :tada:")

Add Emoji reaction / emojiリアクション

  • Add reactions:write scope → Reinstall app

def add_beer_emoji(client, message):
    """add :beer: emoji reaciton"""

Summary of Events and Scopes

まとめ: イベントとスコープ

Case studies 📚


Calculator function using SymPy 🔢

SymPy を使った電卓機能

Calculator function using SymPy

  • Motivation

    • I feel heavy to call a calculator app on my smartphone

    • It seems useful if Slack as a calculator

System overview / システム概要


about SymPy
$ pip install sympy

calc() function using Sympy

from sympy import sympify, SympifyError

def calc(message, context, say):
    """calculator function"""
        formula = context["matches"][0]
        num = sympify(formula)  # Simplifies the formula
        # convert into or float
        answer = int(num) if num.is_Integer else float(num)
    except SympifyError:

Slack as a calculator!! 🎉

Slackが 電卓 になった!!


Plus-plus feature using Peewee ORM 👍

Peewee ORM を使ったプラプラ機能

Plus-plus feature using Peewee ORM

  • Motivation

    • In PyCon JP, I want to make a culture that appreciates each other staff 👍

System overview / システム概要


about Peewee
  • Simple and small ORM.

    • a small, expressive ORM

    • supports sqlite, mysql and postgresql


$ pip install peewee

from peewee import SqliteDatabase, Model, CharField, IntegerField

db = SqliteDatabase("plusplus.db")

class Plusplus(Model):
    name = CharField(primary_key=True)  # fields
    counter = IntegerField(default=0)

    class Meta:
        database = db

db.create_tables([Plusplus], safe=True)

plusplus() function using Peewee

from plusplus_model import Plusplus

@app.message(re.compile(r"^(\w+)\+\+")) # match "word++" pattern
def plusplus(say, context):
    """increments a counter with a name"""
    name = context["matches"][0]
    # Get or create object
    plus, created = Plusplus.get_or_create(
        defaults={'counter': 0},
    plus.counter += 1
    say(f"Thank you {name}! (count: {plus.counter})")

I can appreciate it! 🎉

感謝 できる!!


Search issues with Jira APIs 🔎

Jira API で課題を検索

Search issues with Jira APIs

  • Motivation

    • Jira is very useful

    • Jira Web is slow

    • Search issues without Jira Web

System overview / システム概要


about Python Jira

$ pip install jira

Authentication/ 認証

from jira import JIRA

url = ""
jira = JIRA(url, basic_auth=("email", "API token"))

Search issues / 課題を検索

@app.message(re.compile(r"^jira (.*)$"))
def jira_search(message, context, say):
    keywords = context["matches"][0]
    jql = f'text ~ "{keywords}" order by created desc'
    text = ""
    # get 5 recent issues
    for issue in jira.search_issues(jql, maxResults=5):
        issue_id = issue.key
        url = issue.permalink()
        summary = issue.fields.summary
        text += f"* <{url}|{issue_id}> {summary}\n"
    if not text:
        text = "No issues found"

Free from Jira web! 🎉

Jira webからの 解放!


Create multiple issues from a template 📝

テンプレートから 複数の課題 を作成

Create multiple issues from a template

  • Motivation

    • In pycamp event, 20+ issues are required for each event

    • Copying issues by hand is painful

    • Jira Web is slow (again)

System overview / システム概要


Google Authorization is Complex


  • Create a Google Cloud project

    • Enable API(in this case: Google Sheets API)

    • Download credentials.json

  • Install Google Client Library

    $ pip install google-api-python-client \
      google-auth-httplib2 google-auth-oauthlib
  • Download from GitHub

Google Authorization is Complex

  • Run

    • Select your Google account in Web browser

    • Click “Accept” button

    • Get token.json (finish!!)

$ python
Please visit this URL to authorize this application:
Name, Major:
Alexandra, English

Issue template / 課題テンプレート


Get Spreadsheet data


from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

SCOPES = [""]

@app.message("create issues")
def create_issues(message, say):
    creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    service = build('sheets', 'v4', credentials=creds)
    sheet = service.spreadsheets()
    result = sheet.values().get(spreadsheetId=SHEET, range="A2:C4").execute()
    for row in result.get("values", []):
        say(f"* Title: {row[0]}, Delta: {row[1]}")

Get Spreadsheet data

../_images/bot-sheet12.png ../_images/bot-issue-template21.png

Create Jira issues / 課題を作成

def create_issues(message, say):
    # --snip--
    today =
    for row in result.get("values", []):
        duedate = today + datetime.timedelta(days=int(row[1]))
        issue_dict = {"project": {"key": "ISSHA"},
                      "summary": row[0],
                      "description": row[2],
                      "duedate": f"{duedate:%Y-%m-%d}",
                      "issuetype": {"name": "Task"}}
        issue = jira.create_issue(fields=issue_dict)
        url = issue.permalink()
        say(f"* Create: <{url}|{issue.key}> {row[0]}\n")

Free from copying issues! 🎉

課題のコピーからの 解放!

../_images/bot-sheet22.png ../_images/bot-sheet32.png

Account management of Google Workspace 👥

Google Workspaceでの アカウント管理

Account management of Google Workspace

  • Motivation

    • PyCon JP Association use domain with Google Workspace

    • I only use Google Admin web occasionally

    • I forgot to use admin screen

System overview / システム概要


Update Google Authorization

Google 認証を更新

  • Update a Google Cloud project

    • add Directory API

    • re-download credentials.json

  • Remove token.json

  • Add Directory API

    • Re-run

    • Get new token.json

Get user list / ユーザー一覧を取得

SCOPES = [""]
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
service = build("admin", "directory_v1", credentials=creds)

def user_list(message, say):
    """get Google Workspace users list"""
    users_list = service.users().list(
        orderBy="email", maxResults=10,
    for user in users_list.get("users", []):
        email = user["primaryEmail"]
        fullname = user["name"]["fullName"]
        say(f"* {email} {fullname}")

Get user list / ユーザー一覧を取得


Add user / ユーザー追加

# user_add takanory hogehoge Takanori Suzuki
@app.message(re.compile(r"user_add (\w+) (\w+) (\w+) (\w+)"))
def insert_user(message, say, context):
    """insert Google Workspace user"""
    body = {
        "primaryEmail": context["matches"][0] + "",
        "password": context["matches"][1],
        "name": {
            "givenName": context["matches"][2],
            "familyName": context["matches"][3],
    say(f"User {body['primaryEmail']} created")

Add user / ユーザー追加

../_images/bot-user-add3.png ../_images/bot-user-add22.png

I can forget Google Admin! 🎉

Google Adminを 忘れられる!

Security Issue / セキュリティ課題 🔓

  • Anyone can run it

  • Run only Slack Admin 🔒

Only Admin can run / 管理者 のみ実行可

def is_admin(client, user_id):
    # get user info:
    response = client.users_info(user=user_id)

def user_list(message, say, client):
    """get Google Workspace users list"""
    if not is_admin(client, message["user"]):
        say("You are not an admin!")

Resolve a security issue 🎊

セキュリティ上の課題も 解決

Summary / まとめ 📋

  • Simple bot using Incoming Webhooks

  • Interactive bot using Bolt for Python

  • Extend bot using libraries and APIs

Next Step / 次のステップ 🪜

  • Let’s make your Slackbot

  • Let’s connect with libraries and APIs

  • Automate your Boring Stuff with Slackbot

Thank you! 🙏



sample code

translate command

$ pip install deepl
import deepl

translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"])

@app.message(re.compile(r"^translate (.*)"))
def translate(message, context, say):
    """translate text into English"""
    text = context["matches"][0]
    result = translator.translate_text(

Thank you! 🙏



sample code