Skip to content

Commit 55434f7

Browse files
committed
Add initial files
1 parent 1b8dfd9 commit 55434f7

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

README.md

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# EuroPython 2021 Training
2+
3+
This is the repository with materials for the [Building a practical Slack bot with Python & FastAPI
4+
](https://ep2021.europython.eu/talks/9xzPSHe-building-a-practical-slack-bot-with-python-fastapi-training/) training.
5+
6+
## Step 0 - OS setup
7+
8+
This training is using the following OS tools:
9+
10+
1. `curl` - for making HTTP calls.
11+
1. `jq` - for pretty printing JSON results.
12+
13+
But in case you don't have them, it's not a problem and we don't need them for the final solution.
14+
15+
## Step 1 - Python setup
16+
17+
To run everything from here, we recommend the following Python setup:
18+
19+
1. Use the latest Python version (`3.9.6` by the time of writing).
20+
1. Create a fresh virtual environment for the training.
21+
1. Create a fresh directory to contain everything for this training.
22+
23+
To achieve the points above, use the tools that you use everyday.
24+
25+
We recommend using:
26+
27+
1. [pyenv](https://github.com/pyenv/pyenv)
28+
1. [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)
29+
30+
## Step 2 - Slack setup
31+
32+
We are going to need a test Slack workspace for this training.
33+
34+
The best approach here is to create a brand new one, but feel free to reuse already existing one.
35+
36+
## Step 3 - FastAPI setup
37+
38+
Now, we are going to create one small FastAPI app and expose an endpoint to test with.
39+
40+
First, we need to install the dependencies:
41+
42+
```
43+
pip install fastapi
44+
pip install uvicorn[standard] # or uvicorn\[standard\] depending on your shell
45+
```
46+
47+
Then, in the working directory, create a file called `main.py` with the following contents:
48+
49+
```python
50+
from fastapi import Body, FastAPI, Request, Response
51+
52+
app = FastAPI()
53+
54+
55+
@app.post("/echo")
56+
async def echo(request: Request, response: Response, data=Body(...)):
57+
raw_body = await request.body()
58+
body = raw_body.decode("utf-8")
59+
60+
print(data)
61+
62+
return {
63+
"data": data,
64+
"raw_body": body,
65+
"headers": request.headers
66+
}
67+
```
68+
69+
Now, lets run our server:
70+
71+
```
72+
uvicorn main:app --reload
73+
```
74+
75+
And test it by issuing a POST request via `curl` (or any other HTTP client that you find suitable):
76+
77+
```bash
78+
curl -s -X POST -H "Content-Type: application/json" -d '{"key": "value"}' http://localhost:8000/echo
79+
```
80+
81+
Few things to note:
82+
83+
1. We are using `-s` for a "silent" curl output, skipping the progress bar.
84+
1. We are sending the `Content-Type` header, set to `application/json`, because this triggers FastAPI to parse the JSON into the `body` argument.
85+
86+
An example response would look like that:
87+
88+
```json
89+
{
90+
"data": {
91+
"key": "value"
92+
},
93+
"raw_body": "{\"key\": \"value\"}",
94+
"headers": {
95+
"host": "localhost:8000",
96+
"user-agent": "curl/7.58.0",
97+
"accept": "*/*",
98+
"content-type": "application/json",
99+
"content-length": "16"
100+
}
101+
}
102+
```
103+
104+
## Step 4 - Slack app setup
105+
106+
Now, we are going to setup an app within our new Slack workspace, so we can start communicating with our server.
107+
108+
1. Navigate to <https://api.slack.com/>
109+
1. Click on the `Create an app` button
110+
1. Click on the `Create New App` button
111+
1. Select `From scratch`
112+
1. Type `EuroPython Bot` in the `App Name` field
113+
1. Select your newly created workspace
114+
115+
After following those steps, you should end up with a screen that looks something like that:
116+
117+
![image](https://user-images.githubusercontent.com/387867/126795187-59ed6d31-e0a2-4ebe-9713-727a525b4762.png)
118+
119+
Before we continue further, we'll need 1 more thing.
120+
121+
## Step 5 - ngrok setup
122+
123+
Since we are going to listen for events from Slack & those events are going to be HTTP POST requests, we need a way to tell Slack how to find our `localhost:8000` server.
124+
125+
One way of doing this is using [`ngrok`](https://ngrok.com/) - a piece of software that's going to create a tunnel to our localhost & give us a public url, that we'll post in Slack.
126+
127+
Navigate to the [download page](https://ngrok.com/download) and download ngrok for you OS & platform.
128+
129+
Once extracted, in a new shell, while our FastAPI server is running, type:
130+
131+
```
132+
./ngrok http 8000
133+
```
134+
135+
This will run `ngrok` and present us with the public url. Copy that url.
136+
137+
## Step 6 - Testing if we have integrated correctly
138+
139+
Now, back to Slack setup:
140+
141+
1. Go to the `Event Subscriptions` option.
142+
1. Turn it on.
143+
1. Paste your `ngrok` url, pointing to the `/echo` endpoint. In my case, that's `https://c153a68641fd.ngrok.io/echo`
144+
1. If our server & `ngrok` are running, you'll see a green `Verified` in Slack. Check the `ngrok` shell - you should see a request there.
145+
1. Now click on `Subscribe to bot events`, click `Add Bot User Event` and select `app_mention`.
146+
1. Hit `Save Changes`.
147+
1. Leave the page open, since we'll come back here to add additional things.
148+
149+
Now, lets wire everything together, so we can start developing:
150+
151+
1. In the app settings, navigate to `Settings` -> `Basic Information`
152+
1. Click the `Install to Workspace` button.
153+
1. Click `Allow`.
154+
1. Every time we change the permissions or scopes, we'll have to redo this entire process.
155+
156+
Now, open up the Slack workspace:
157+
158+
1. Add the bot (type `/add` in the channel & click on the action item) to a random channel.
159+
1. `@` the bot and write something.
160+
1. Check your FastAPI console.
161+
162+
The event payload that we received should look something like that:
163+
164+
```json
165+
{
166+
"token": "C2lhacRaergBLDrNglaNtVYQ",
167+
"team_id": "T028KF61U7R",
168+
"api_app_id": "A0290D2N9B4",
169+
"event": {
170+
"client_msg_id": "7f025cc5-2f1c-41c9-9ad9-f78d86c13b49",
171+
"type": "app_mention",
172+
"text": "<@U0290LLS803> hello :wave:",
173+
"user": "U028TEER6KY",
174+
"ts": "1627050528.001000",
175+
"team": "T028KF61U7R",
176+
"blocks": [
177+
{
178+
"type": "rich_text",
179+
"block_id": "UBr7f",
180+
"elements": [
181+
{
182+
"type": "rich_text_section",
183+
"elements": [
184+
{
185+
"type": "user",
186+
"user_id": "U0290LLS803"
187+
},
188+
{
189+
"type": "text",
190+
"text": " hello "
191+
},
192+
{
193+
"type": "emoji",
194+
"name": "wave"
195+
}
196+
]
197+
}
198+
]
199+
}
200+
],
201+
"channel": "C028TEHTWKG",
202+
"event_ts": "1627050528.001000"
203+
},
204+
"type": "event_callback",
205+
"event_id": "Ev029Q5P36BA",
206+
"event_time": 1627050528,
207+
"authorizations": [
208+
{
209+
"enterprise_id": null,
210+
"team_id": "T028KF61U7R",
211+
"user_id": "U0290LLS803",
212+
"is_bot": true,
213+
"is_enterprise_install": false
214+
}
215+
],
216+
"is_ext_shared_channel": false,
217+
"event_context": "3-app_mention-T028KF61U7R-A0290D2N9B4-C028TEHTWKG"
218+
}
219+
```
220+
221+
This means we are now ready with our setup and can start adding funcitonality back.
222+
223+
## Step 7 - Implementing bot behavior (TASKS START HERE)
224+
225+
Now, it's time for our tasks.
226+
227+
**We want to implement the following general behavior:**
228+
229+
1. Whenever someone mentions our bot, we want to reply in the channel with a message.
230+
1. Whenever someone mentions our bot **in a thread**, we want to reply in the same thread with a message.
231+
232+
In order to do that, we want to be making HTTP calls to the Slack API via `requests`, so we need to do:
233+
234+
```
235+
pip install requests
236+
```
237+
238+
### Documentation links to help
239+
240+
Extracted documentation links to help you navigate to the proper stuff to look at:
241+
242+
1. [What is a Slack app?](https://api.slack.com/authentication/basics#start)
243+
1. [Listening to the `app_mention` event](https://api.slack.com/events/app_mention)
244+
1. [Sending messages](https://api.slack.com/messaging/sending)
245+
1. [Retrieving individual messages](https://api.slack.com/messaging/retrieving#individual_messages)
246+
247+
248+
## Step 8 - Validate Slack requests
249+
250+
Everything is great, but our API is public, meaning anyone can call it and start sending messages to our Slack.
251+
252+
**We want to prevent this & your next task is to implement request verification!**
253+
254+
### Documentation links to help
255+
256+
1. [Verifying requests from Slack](https://api.slack.com/authentication/verifying-requests-from-slack)
257+
258+
## Step 9 - Wrapping it up and further references
259+
260+
That's about it.
261+
262+
Materials for further references:
263+
264+
1. There's an offical [Python Slack SDK](https://github.com/slackapi/python-slack-sdk) that you can use.
265+
1. You can also use [`bolt-python`](https://github.com/slackapi/bolt-python) which is a framework for building Slack apps.
266+
1. [The official documentation, of course](https://api.slack.com/)

main.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from fastapi import Body, FastAPI, Request, Response
2+
3+
app = FastAPI()
4+
5+
6+
@app.post("/echo")
7+
async def echo(request: Request, response: Response, data=Body(...)):
8+
raw_body = await request.body()
9+
body = raw_body.decode("utf-8")
10+
11+
print(data)
12+
13+
return {
14+
"data": data,
15+
"raw_body": body,
16+
"headers": request.headers
17+
}

0 commit comments

Comments
 (0)