Skip to content

A Slack bot which allows users to add multiple reactions to a message at the same time.

License

Notifications You must be signed in to change notification settings

ccaruceru/slack-multireact

Repository files navigation

Table of contents


About

logo

A Slack bot which allows users to add multiple reactions to a message at the same time.

Written in Python with Slack Bolt for Python.

Stores data in Google Cloud Storage buckets

Tests Docker Image Version (latest by date)

Usage

The bot exposes two APIs: a /multireact command and a Multireact message Shortcut.

Examples

  • /multireact to view saved the reactions

    reaction-view
  • /multireact 🤠😎😼➕💯 to set a list of reactions

    reaction-save
  • Add reactions on a message by going to More Actions -> More message shortcuts -> Multireact

    1. reaction-none
    2. reaction-menu
    3. reaction-add

Docker image

Run the ccaruceru/slack-multireact docker image, or build the image locally with docker build -t multireact .

Example:

docker run -e SLACK_CLIENT_ID=clientid -e SLACK_CLIENT_SECRET=clientsecret -e SLACK_SIGNING_SECRET=signingsecret -e SLACK_INSTALLATION_GOOGLE_BUCKET_NAME=slack-multireact-installation -e SLACK_STATE_GOOGLE_BUCKET_NAME=slack-multireact-oauthstate -e USER_DATA_BUCKET_NAME=slack-multireact-userdata -p 3000:3000 -v /path/to/sa-multireact-key.json:/credentials.json ccaruceru/slack-multireact

For a complete list of required environment variables, see Environment variables section. To find out what permissions you need for the Slack bot, see Slack Application section.

Google Cloud deployment

The deployment process consists in creating two Google Cloud components: A Google App Engine Service using Standard environment, and several Buckets.

Google Storage Buckets

The application requires 3 GCS buckets to store temporary data for: the OAuth process, app installation data for each user, user emoji data.

Sample commands to create the buckets:

gsutil mb -c STANDARD -l <region> -b on gs://slack-multireact-userdata

gsutil mb -c STANDARD -l <region> -b on gs://slack-multireact-oauthstate

gsutil mb -c STANDARD -l <region> -b on gs://slack-multireact-installation

Optional: set a retention policy of 1 day for the bucket that will be used for temporary OAuth tokens:

gsutil lifecycle set oauth-bucket-lifecycle.json gs://slack-multireact-oauthstate

Google App Engine

ℹ Note: Cloud Build API must be enabled for the project:

gcloud services enable cloudbuild.googleapis.com

Initialize App Engine app for the project (docs):

gcloud app create --region=<region> --project=<project-id>

ℹ Note: It is recommended to stick to the same region as the buckets.

A new service account with the following naming convention is created: PROJECT_ID@appspot.gserviceaccount.com (docs).

Grant permissions for each bucket for the App Engine service account:

gsutil iam ch serviceAccount:<project-id>@appspot.gserviceaccount.com:roles/storage.objectAdmin gs://slack-multireact-userdata

gsutil iam ch serviceAccount:<project-id>@appspot.gserviceaccount.com:roles/storage.objectAdmin gs://slack-multireact-oauthstate

gsutil iam ch serviceAccount:<project-id>@appspot.gserviceaccount.com:roles/storage.objectAdmin gs://slack-multireact-installation

Install required deployment dependencies:

gcloud components install app-engine-python

Copy the app.yaml.template files and adjust the contents in the ### Change this section part.

cp app.yaml.template default.yaml

⚠ WARNING: The first app that is going to be deployed in the App Engine must be deployed as the default service (docs).

Deploy the service:

gcloud app deploy default.yaml --project=<project-id> --version=my-version

where my version may only contain lowercase letters, digits, and hyphens (e.g. a git tag like 1-20-0).

Find the service endpoint with:

gcloud app browse --no-launch-browser --service=default --project=<project-id>

Multiple apps or environments

To deploy the bot multiple times in the same App Engine for different purposes (like test, stage and prod environments), copy app.yaml.template for each app, then change the contents in the ### Change this section part (and the service: key in the file).

e.g.

cp app.yaml.template default.yaml
cp app.yaml.template sandbox.yaml

Deploy the app specifying each yaml configuration file:

gcloud app deploy default.yaml sandbox.yaml --project=<project-id>

Find each sevice endpoint with:

gcloud app browse --no-launch-browser --service=default
gcloud app browse --no-launch-browser --service=sandbox

Cleanup old services

Google App Engine is versioning the app for each deployment and creates an endpoint for each version, then all the traffic is routed to the latest deployed service (unless specified otherwise) (docs).

List all running versions:

gcloud app versions list

e.g. output

SERVICE  VERSION.ID       TRAFFIC_SPLIT  LAST_DEPLOYED              SERVING_STATUS
default  20210521t201306  0.00           2021-05-21T20:14:28+02:00  SERVING
default  20210521t203524  0.00           2021-05-21T20:36:35+02:00  SERVING
default  20210521t204813  0.00           2021-05-21T20:49:12+02:00  SERVING
default  20210521t210402  1.00           2021-05-21T21:04:58+02:00  SERVING
sandbox  20210521t201306  0.00           2021-05-21T20:15:51+02:00  SERVING
sandbox  20210521t203524  0.00           2021-05-21T20:37:49+02:00  SERVING
sandbox  20210521t204813  0.00           2021-05-21T20:50:18+02:00  SERVING
sandbox  20210521t210402  1.00           2021-05-21T21:06:18+02:00  SERVING

The versions where the TRAFFIC_SPLIT is 1.00 represent the latest deployed application. It is safe to delete the ones where TRAFFIC_SPLIT is 0.

gcloud app versions delete <version>

Or use a one liner to delete all versions where TRAFFIC_SPLIT is 0:

gcloud app versions delete $(gcloud app versions list --filter=TRAFFIC_SPLIT=0 --format="value(version.id)" | sort -u | tr '\n' ' ') --quiet

Troubleshooting

Problem: gcloud app deploy fails with the following error:

ERROR: (gcloud.app.deploy) NOT_FOUND: Unable to retrieve P4SA: [service-236999523341@gcp-gae-service.iam.gserviceaccount.com] from GAIA. Could be GAIA propagation delay or request from deleted apps.

Solution: Try again in a few seconds.

Slack Application

The following sections must be set:

Interactivity and Shortcuts

  • Add <bot address>/slack/events to Request URL (can be added after the Service has been deployed)

  • Create New Shortcut

    • with On messages type
    • set the Name to Multireact
    • add a short description
    • set the Callback ID to add_reactions
    create-shortcut

Slash commands

  • Create New Command

    • Command is /multireact
    • Request URL is <bot address>/slack/events (can be added after the Service has been deployed)
    • and add a Short description and a usage hint
    create-command

OAuth and Permissions

  • Add New Redirect URL and use <bot address>/slack/oauth_redirect (can be added after the Service has been deployed)

    redirect-url
  • Go to Scopes section and -> Bot Token Scopes and add an OAuth scope for commands (might be already added)

    add-scope
  • Press the Opt in button to add extra security for the application

    token-rotation

Event Subscriptions

  • enable Events

    event-enable
  • add <bot address>/slack/events under Request URL (can be added after the Service has been deployed)

  • expand Subscribe to bot events, click on Add Bot User Event and add the following events:

    • app_home_opened
    • app_uninstalled
    • tokens_revoked
    event-subscription

App Home

Enable Home Tab and disable everything else.

app-home

Basic Information

Add relevant description under Display Information

Distributing the app

After all information has been set and the application is deployed, users are now able to authorize and use the bot by heading to <bot address>/slack/install endpoint in their browsers (e.g. https://my-multireact-app.com/slack/install). Administrators should distribute the bot address to the users once the application is up and running (see Docker image or Google Cloud deployment sections).

Environment variables

Mandatory environment variables for the App Engine Service / Docker image are taken from the app's Basic Information page (see Slack Application section):

  • SLACK_CLIENT_ID: the Client ID

  • SLACK_CLIENT_SECRET: the Client Secret

  • SLACK_SIGNING_SECRET: the Signing Secret

    app-credentials
  • GOOGLE_APPLICATION_CREDENTIALS: path to a json file with credentials for an account with permissions to GCS buckets (e.g. sa-multireact-key.json). Not required if the bot is running inside Google Cloud.

  • SLACK_INSTALLATION_GOOGLE_BUCKET_NAME: name of a bucket used to store Slack app install data per user (e.g. slack-multireact-installation)

  • SLACK_STATE_GOOGLE_BUCKET_NAME: bucket name for storing temporary OAuth state (e.g. slack-multireact-oauthstate)

  • USER_DATA_BUCKET_NAME: bucket for user emoji data (e.g. slack-multireact-userdata)

Optional environment variables:

  • SLACK_SLASH_COMMAND: specifies what is the slash command the app should listen for. Useful for multiple deployment in the same Slack workspace. Defaults to /multireact
  • Uvicorn ASGI variables, like:
    • UVICORN_PORT: specify a server socket to bind. Defaults to 3000.
    • UVICORN_WORKERS: the number of worker processes. Defaults to 1.
    • UVICORN_LOG_LEVEL: log verosity. defaults to info.
    • UVICORN_HOST: network interfaces to bind the server. Default to 0.0.0.0.

Development

Make sure you have at least Python version 3.10, ngrok and Google Cloud SDK, then run:

  • pip install -r requirements.txt
  • in a sepparate terminal run ngrok http 3000 and take a note of the ngrok generated https address
    • ℹ Note: if there is a VPN client running, ngrok might fail to establish a connection
  • setup a slack application according to Slack Application section, using ngrok generated https address
    • the HTTP endpoints created by Bolt framework are:
      • /slack/events - used as Request URL for incoming slack API requests (commands and shortcuts)
      • /slack/install - simple interface which allows a user to install the app to a workspace and start the OAuth flow
      • /slack/oauth_redirect - endpoint used by Slack to complete the OAuth flow (the Redirect URL under OAuth and Permissions section)
  • create GCS buckets as described in Google Storage buckets
  • create a service account, grant access to the Google Cloud Storage buckets (like in Google App Engine section), and generate a key for the account:
# create svc account
gcloud iam service-accounts create sa-multireact-dev --description="svc account with access to GCS buckets" --display-name="SA Multireact Dev" --project=<project-id>
# get svc account full name
gcloud iam service-accounts list
# <grant access to buckets>
# generate key
gcloud iam service-accounts keys create sa-multireact-key.json --iam-account=sa-multireact-dev@<project-id>.iam.gserviceaccount.com
  • set environment variables according to Environment variables section. Optionally, set a PORT number (defaults to 3000)
  • uvicorn multi_reaction_add.handlers:api --port 3000 --reload to run the app
  • go to "ngrok generated https address/slack/install" to install the app to the workspace and start interracting like in the Usage section.

Debugging with VS Code

Use the following .vscode/launch.json file to setup a debug configuration for the app:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Slack Bot",
            "type": "python",
            "request": "launch",
            "module": "uvicorn",
            "args": ["multi_reaction_add.handlers:api", "--port", "3000", "--no-access-log"],
            "console": "integratedTerminal",
            "env": {
                "SLACK_CLIENT_ID": "clientid",
                "SLACK_CLIENT_SECRET": "clientsecret",
                "SLACK_SIGNING_SECRET": "signingsecret",
                "GOOGLE_APPLICATION_CREDENTIALS": "sa-multireact-key.json",
                "SLACK_INSTALLATION_GOOGLE_BUCKET_NAME": "slack-multireact-installation",
                "SLACK_STATE_GOOGLE_BUCKET_NAME": "slack-multireact-installation",
                "USER_DATA_BUCKET_NAME": "slack-multireact-installation",
                "UVICORN_LOG_LEVEL": "info"
            }
        }
    ]
}

Then press F5 to start debugging.

Linting

Use pylint to run static code analysis and check if the code has well formatted docstrings according to Google style. Code rate should always be 10.00/10.

pip install pylint

pylint tests multi_reaction_add multi_reaction_add/oauth/installation_store/google_cloud_storage multi_reaction_add/oauth/state_store/google_cloud_storage

Testing and code coverage

The tests folder contains unit tests for the app logic. You can run the tests from commandline with:

python -m unittest discover

To generate a code coverage report, you need to install coverage package, use it to run the tests and then you'll be able to view the coverage report. The code coverage should always be 99%.

# install package
pip install coverage

# run the tests
coverage run --source=multi_reaction_add -m unittest discover

# view report
coverage report -m

Use the following .vscode/settings.json configuration to setup testing with unittest for VS Code:

{
    "python.testing.unittestArgs": [
        "-v",
        "-s",
        "./tests",
        "-p",
        "test_*.py"
    ],
    "python.testing.pytestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.unittestEnabled": true
}

More

The app uses ASGI with starlette and the application is handling requests with uvicorn, which works on top of Python's concurrent library asyncio. The choice was mostly based on the results shown in this post.

The application exposes two additional http endpoints: /img for serving static images, and /_ah/warmup for GAE warmup requests.

More info about how to setup a local environment can be found here, documentation about the Slack Bolt for Python APIs can be found here, and more examples on how to use the Bolt framework can be found here.

About

A Slack bot which allows users to add multiple reactions to a message at the same time.

Resources

License

Stars

Watchers

Forks

Packages

No packages published