- About
- Usage
- Docker image
- Google Cloud deployment
- Slack Application
- Environment variables
- Development
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
The bot exposes two APIs: a /multireact
command and a Multireact
message Shortcut.
-
/multireact
to view saved the reactions -
/multireact 🤠😎😼➕💯
to set a list of reactions -
Add reactions on a message by going to
More Actions
->More message shortcuts
->Multireact
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.
The deployment process consists in creating two Google Cloud components: A Google App Engine Service using Standard environment, and several 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
ℹ 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>
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
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
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.
The following sections must be set:
-
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 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
- Command is
-
Add New Redirect URL and use
<bot address>/slack/oauth_redirect
(can be added after the Service has been deployed) -
Go to Scopes section and -> Bot Token Scopes and add an OAuth scope for
commands
(might be already added) -
Press the
Opt in
button to add extra security for the application
-
enable Events
-
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
Enable Home Tab
and disable everything else.
Add relevant description under Display Information
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).
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
-
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
.
- UVICORN_PORT: specify a server socket to bind. Defaults to
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)
- the HTTP endpoints created by Bolt framework are:
- 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.
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.
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
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
}
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.