Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/_basic/acknowledging_events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Acknowledging events
lang: en
slug: acknowledge
order: 7
---

<div class="section-content">

Actions, commands, and options events must **always** be acknowledged using the `ack()` function. This lets Slack know that the event was received and updates the Slack user interface accordingly.

Depending on the type of event, your acknowledgement may be different. For example, when acknowledging a menu selection associated with an external data source, you would call `ack()` with a list of relevant [options](https://api.slack.com/reference/block-kit/composition-objects#option).

We recommend calling `ack()` right away before sending a new message or fetching information from your database since you only have 3 seconds to respond.

</div>

```python
# Example of responding to an external_select options request
@app.options("menu_selection")
def show_menu_options(ack):
options = [
{
"text": {"type": "plain_text", "text": "Option 1"},
"value": "1-1",
},
{
"text": {"type": "plain_text", "text": "Option 2"},
"value": "1-2",
},
]
ack(options=options)
```
94 changes: 94 additions & 0 deletions docs/_basic/authenticating_oauth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: Authenticating with OAuth
lang: en
slug: authenticating-oauth
order: 14
---

<div class="section-content">

Slack apps installed on multiple workspaces will need to implement OAuth, then store installation information (like access tokens) securely. By providing `client_id`, `client_secret`, `scopes`, `installation_store`, and `state_store` when initializing App, Bolt for Python will handle the work of setting up OAuth routes and verifying state. If you’re implementing a custom receiver, you can make use of our [OAuth library](https://slack.dev/python-slack-sdk/oauth/), which is what Bolt for Python uses under the hood.

Bolt for Python will create a **Redirect URL** `slack/oauth_redirect`, which Slack uses to redirect users after they complete your app’s installation flow. You will need to add this **Redirect URL** in your app configuration settings under **OAuth and Permissions**. This path can be configured in the `OAuthSettings` argument described below.

Bolt for Python will also create a `slack/install` route, where you can find an **Add to Slack** button for your app to perform direct installs of your app. If you need any additional authorizations (user tokens) from users inside a team when your app is already installed or a reason to dynamically generate an install URL, you can pass your own custom URL generator to `oauth_settings` as `authorize_url_generator`.

To learn more about the OAuth installation flow with Slack, [read the API documentation](https://api.slack.com/authentication/oauth-v2).

</div>

```python
import os
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore

oauth_settings = OAuthSettings(
client_id=os.environ["SLACK_CLIENT_ID"],
client_secret=os.environ["SLACK_CLIENT_SECRET"],
scopes=["channels:read", "groups:read", "chat:write"],
installation_store=FileInstallationStore(base_dir="./data"),
state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data")
)

app = App(
signing_secret=os.environ["SIGNING_SECRET"],
oauth_settings=oauth_settings
)
```

<details class="secondary-wrapper">
<summary class="section-head" markdown="0">
<h4 class="section-head">Customizing OAuth defaults</h4>
</summary>

<div class="secondary-content" markdown="0">
You can override the default OAuth using `oauth_settings`, which can be passed in during the initialization of App. You can override the following:

- `install_path`: Override default path for "Add to Slack" button
- `redirect_uri`: Override default redirect url path
- `callback_options`: Provide custom success and failure pages at the end of the OAuth flow
- `state_store`: Provide a custom state store instead of using the built in `FileOAuthStateStore`
- `installation_store`: Provide a custom installation store instead of the built-in `FileInstallationStore`

</div>

```python
from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs
import slack_bolt.response.BoltResponse

def success(args: SuccessArgs) -> BoltResponse:
assert args.request is not None
return BoltResponse(
status=200, # you can redirect users too
body="Your own response to end-users here"
)

def failure(args: FailureArgs) -> BoltResponse:
assert args.request is not None
assert args.reason is not None
return BoltResponse(
status=args.suggested_status_code,
body="Your own response to end-users here"
)

callback_options = CallbackOptions(success=success, failure=failure)

app = App(
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
installation_store=FileInstallationStore(base_dir="./data"),
oauth_settings=OAuthSettings(
client_id=os.environ.get("SLACK_CLIENT_ID"),
client_secret=os.environ.get("SLACK_CLIENT_SECRET"),
scopes=["app_mentions:read", "channels:history", "im:history", "chat:write"],
user_scopes=[],
redirect_uri=None,
install_path="/slack/install",
redirect_uri_path="/slack/oauth_redirect",
state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data"),
callback_options=callback_options,
),
)
```

</details>
30 changes: 0 additions & 30 deletions docs/_basic/example.md

This file was deleted.

46 changes: 46 additions & 0 deletions docs/_basic/listening_actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Listening to actions
lang: en
slug: action-listening
order: 5
---

<div class="section-content">
Your app can listen to user actions, like button clicks, and menu selects, using the `action` method.

Actions can be filtered on an `action_id` of type `str` or `re.Pattern`. `action_id`s act as unique identifiers for interactive components on the Slack platform.

You’ll notice in all `action()` examples, `ack()` is used. It is required to call the `ack()` function within an action listener to acknowledge that the event was received from Slack. This is discussed in the [acknowledging events section](#acknowledge).

</div>

```python
# Your middleware will be called every time an interactive component with the action_id "approve_button" is triggered
@app.action("approve_button")
def update_message(ack):
ack()
# Update the message to reflect the action
```

<details class="secondary-wrapper">
<summary class="section-head" markdown="0">
<h4 class="section-head">Listening to actions using a constraint object</h4>
</summary>

<div class="secondary-content" markdown="0">

You can use a constraints object to listen to `callback_id`s, `block_id`s, and `action_id`s (or any combination of them). Constraints in the object can be of type `str` or `re.Pattern`.

</div>

```python
# Your function will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket'
@app.action({"action_id": "select_user", "block_id": "assign_ticket"})
def update_message(ack, action, body, client):
ack()
client.reactions_add(name='white_check_mark',
timestamp=action['action_ts'],
channel=body['channel']['id'])
```

</details>
46 changes: 46 additions & 0 deletions docs/_basic/listening_events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Listening to events
lang: en
slug: event-listening
order: 3
---

<div class="section-content">

You can listen to [any Events API event](https://api.slack.com/events) using the `event()` method after subscribing to it in your app configuration. This allows your app to take action when something happens in a workspace where it's installed, like a user reacting to a message or joining a channel.

The `event()` method requires an `eventType` of type `str`.

</div>

```python
# When a user joins the team, send a message in a predefined channel asking them to introduce themselves
@app.event("team_join")
def ask_for_introduction(event, say):
welcome_channel_id = "C12345";
user_id = event["user"]["id"]
text = f"Welcome to the team, <@{user_id}>! 🎉 You can introduce yourself in this channel."
say(text=text, channel=welcome_channel_id)
```

<details class="secondary-wrapper" >

<summary class="section-head" markdown="0">
<h4 class="section-head">Filtering on message subtypes</h4>
</summary>

<div class="secondary-content" markdown="0">
The `message()` listener is equivalent to `event("message")`.

You can filter on subtypes of events by passing in the additional key `subtype`. Common message subtypes like `bot_message` and `message_replied` can be found [on the message event page](https://api.slack.com/events/message#message_subtypes).

</div>

```python
# Matches all messages from bot users
@app.event({"type": "message", "subtype": "message_changed"})
def log_message_change(logger, event):
logger.info(f"The user {event['user']} changed the message to {event['text']}")
```

</details>
43 changes: 43 additions & 0 deletions docs/_basic/listening_messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Listening to messages
lang: en
slug: message-listening
order: 1
---

<div class="section-content">

To listen to messages that [your app has access to receive](https://api.slack.com/messaging/retrieving#permissions), you can use the `message()` method which filters out events that aren’t of type `message`.

`message()` accepts an argument of type `str` or `re.Pattern` object that filters out any messages that don’t match the pattern.

</div>

```python
# This will match any message that contains 👋
@app.message(":wave:")
def say_hello(message, say):
user = message['user']
say(f"Hi there, <@{user}>!")
```

<details class="secondary-wrapper">
<summary markdown="0">
<h4 class="secondary-header">Using a regular expression pattern</h4>
</summary>

<div class="secondary-content" markdown="0">

The `re.compile()` method can be used instead of a string for more granular matching.

</div>

```python
@app.message(re.compile("(hi|hello|hey)"))
def say_hello_regex(say, context):
# regular expression matches are inside of context.matches
greeting = context['matches'][0]
say(f"{greeting}, how are you?")
```

</details>
53 changes: 53 additions & 0 deletions docs/_basic/listening_modals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: Listening for view submissions
lang: en
slug: view_submissions
order: 12
---

<div class="section-content">

If a <a href="https://api.slack.com/reference/block-kit/views">view payload</a> contains any input blocks, you must listen to `view_submission` events to receive their values. To listen to `view_submission` events, you can use the built-in `view()` method. `view()` requires a `callback_id` of type `str` or `re.Pattern`.

You can access the value of the `input` blocks by accessing the `state` object. `state` contains a `values` object that uses the `block_id` and unique `action_id` to store the input values.

Read more about view submissions in our <a href="https://api.slack.com/surfaces/modals/using#interactions">API documentation</a>.

</div>

```python
# Handle a view_submission event
@app.view("view_b")
def handle_submission(ack, body, client, view):

# Assume there's an input block with `block_1` as the block_id and `input_a`
val = view["state"]["values"]["block_1"]["input_a"]
user = body["user"]["id"]

# Validate the inputs
errors = {}
if val is not None and len(val) <= 5:
errors["block_1"] = "The value must be longer than 5 characters"
if len(errors) > 0:
ack(response_action="errors", errors=errors)
return

# Acknowledge the view_submission event and close the modal
ack()

# Do whatever you want with the input data - here we're saving it to a DB
# then sending the user a verification of their submission

# Message to send user
msg = ""

try:
# Save to DB
msg = f"Your submission of {val} was successful"
except Exception as e:
# Handle error
msg = "There was an error with your submission"
finally:
# Message the user
client.chat_postMessage(channel=user, text=msg)
```
27 changes: 27 additions & 0 deletions docs/_basic/listening_responding_commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Listening and responding to commands
lang: en
slug: commands
order: 9
---

<div class="section-content">

Your app can use the `command()` method to listen to incoming slash command events. The method requires a `command_name` of type `str`.

Commands must be acknowledged with `ack()` to inform Slack your app has received the event.

There are two ways to respond to slash commands. The first way is to use `say()`, which accepts a string or JSON payload. The second is `respond()` which is a utility for the `response_url`. These are explained in more depth in the [responding to actions](#action-respond) section.

When setting up commands within your app configuration, you'll append `/slack/events` to your request URL.

</div>

```python
# The echo command simply echoes on command
@app.command("/echo")
def repeat_text(ack, say, command):
# Acknowledge command request
ack()
say(f"{command['text']}")
```
Loading