Skip to content
Draft
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
9 changes: 9 additions & 0 deletions messaging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Messaging

Core Slack functionality!

Read the [docs](https://docs.slack.dev/messaging/) to learn about messaging in Slack.

## What's on display

- **[Work Objects](https://docs.slack.dev/messaging/work-objects/)**: Preview and interact with your app data in Slack.
6 changes: 6 additions & 0 deletions messaging/work-objects/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
.mypy_cache
.pytest_cache
.ruff_cache
.venv
.slack
40 changes: 40 additions & 0 deletions messaging/work-objects/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Work Objects Showcase

Your app can respond to links being shared in Slack with Work Object metadata to display a link preview. Users can click the preview to view authenticated data in the flexpane. If supported, users can edit app data directly from the flexpane.

You can also [post](https://docs.slack.dev/messaging/work-objects/#implementation-notifications) Work Object metadata directly with `chat.postMessage`, without a link being shared.

Read the [docs](https://docs.slack.dev/messaging/work-objects/) to learn more!

## Setup

### Option 1: Create a new app with the Slack CLI

```bash
slack init && slack run
```

### Option 2: Create a Slack App in the UI and copy over your tokens

Create an app on [https://api.slack.com/apps](https://api.slack.com/apps) from the app manifest in this repo (`manifest.json`).

Configure environment variables:
```bash
# OAuth & Permissions → Bot User OAuth Token
export SLACK_BOT_TOKEN=<your-bot-token>
# Basic Information → App-Level Tokens (create one with the `connections:write` scope)
export SLACK_APP_TOKEN=<your-app-token>
```

Install dependencies and run the app:
```bash
# Setup virtual environment
python3 -m venv .venv
source .venv/bin/activate

# Install the dependencies
pip install -r requirements.txt

# Start the server
python3 app.py
```
97 changes: 97 additions & 0 deletions messaging/work-objects/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import os
import sys
import logging
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk import WebClient
from slack_sdk.models.metadata import EventAndEntityMetadata
from metadata import sample_entities, sample_task_unfurl_url, sample_file_unfurl_url


# Initializes your app with your bot token
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))


# Listens for the link_shared event
# https://docs.slack.dev/reference/events/link_shared/
@app.event("link_shared")
def link_shared_callback(event: dict, client: WebClient, logger: logging.Logger):
try:
for link in event["links"]:
entity = sample_entities[link["url"]]
if entity is not None:
client.chat_unfurl(
channel=event["channel"],
ts=event["message_ts"],
metadata=EventAndEntityMetadata(entities=[entity]),
)
else:
logger.info("No entity found with URL " + link)
except Exception as e:
logger.error(
f"An error occurred while handling the entity_details_requested event: {type(e).__name__} - {str(e)}",
exc_info=e,
)


# Listens for the entity_details_requested event, which is sent when a user opens the flexpane
# https://docs.slack.dev/reference/events/entity_details_requested/
@app.event("entity_details_requested")
def entity_details_callback(event: dict, client: WebClient, logger: logging.Logger):
try:
entity_url = event["entity_url"]
entity = sample_entities[entity_url]
if entity is not None:
client.entity_presentDetails(
trigger_id=event["trigger_id"], metadata=entity
)
else:
logger.info("No entity found with URL " + entity_url)
except Exception as e:
logger.error(
f"An error occurred while handling the entity_details_requested event: {type(e).__name__} - {str(e)}",
exc_info=e,
)


# Listens for the view_submission event sent when the user submits edits in the flexpane
# https://docs.slack.dev/tools/bolt-js/concepts/view-submissions/
# https://docs.slack.dev/messaging/work-objects/#editing-view-submission
@app.view_submission("work-object-edit")
def work_object_edit_callback(view: dict, body: dict, client: WebClient, logger: logging.Logger, ack):
try:
ack()

entity_url = view["entity_url"]
entity = sample_entities[entity_url]

if "title" in view["state"]["values"]:
attributes = entity.entity_payload.entity_attributes
attributes.title.text = view["state"]["values"]["title"]["title.input"]["value"]
entity.entity_payload.entity_attributes = attributes

if "description" in view["state"]["values"]:
entity.entity_payload.fields.description.value = view["state"]["values"]["description"]["description.input"]["value"]

if entity is not None:
client.entity_presentDetails(
trigger_id=body["trigger_id"], metadata=entity
)
else:
logger.info("No entity found with URL " + entity_url)
except Exception as e:
logger.error(
f"An error occurred while handling the entity_details_requested event: {type(e).__name__} - {str(e)}",
exc_info=e,
)


# Start your app
if __name__ == "__main__":
try:
print("Try sharing this link: ", sample_task_unfurl_url)
print("Or this link: ", sample_file_unfurl_url)
SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
except Exception as error:
print(f"Failed to start app: {error}", file=sys.stderr)
sys.exit(1)
33 changes: 33 additions & 0 deletions messaging/work-objects/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"display_information": {
"name": "work_objects_python",
"description": "A sample app demonstrating Slack Work Objects functionality"
},
"features": {
"rich_previews": {
"is_active": true,
"entity_types": ["slack#/entities/task", "slack#/entities/file"]
},
"unfurl_domains": ["work-objects-python.com"],
"bot_user": {
"display_name": "work_objects_python_bot",
"always_online": true
}
},
"oauth_config": {
"scopes": {
"bot": ["chat:write", "links:read", "links:write"]
}
},
"settings": {
"event_subscriptions": {
"bot_events": ["link_shared", "entity_details_requested"]
},
"interactivity": {
"is_enabled": true
},
"org_deploy_enabled": true,
"socket_mode_enabled": true,
"token_rotation_enabled": false
}
}
82 changes: 82 additions & 0 deletions messaging/work-objects/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from slack_sdk.models.metadata import (
EntityMetadata,
EntityType,
ExternalRef,
EntityPayload,
EntityAttributes,
EntityTitle,
TaskEntityFields,
EntityStringField,
EntityTitle,
EntityAttributes,
TaskEntityFields,
EntityStringField,
EntityEditSupport,
EntityCustomField,
ExternalRef,
EntityFullSizePreview
)

# Update the URL here to match your app's domain
sample_task_unfurl_url = "https://work-objects-python.com/task"
sample_file_unfurl_url = "https://work-objects-python.com/file"

sample_entities = {
sample_task_unfurl_url: EntityMetadata(
entity_type="slack#/entities/task",
url=sample_task_unfurl_url,
app_unfurl_url=sample_task_unfurl_url,
external_ref=ExternalRef(id="sample_task_id"),
entity_payload=EntityPayload(
attributes=EntityAttributes(
title=EntityTitle(
text="My Task",
edit=EntityEditSupport(enabled=True),
)
),
fields=TaskEntityFields(
description=EntityStringField(
value="Hello World!",
edit=EntityEditSupport(enabled=True)
)
),
custom_fields=[
EntityCustomField(
key="hello-world",
label="hello-world",
value="hello-world",
type="string",
)
],
),
),
sample_file_unfurl_url: EntityMetadata(
entity_type="slack#/entities/file",
url=sample_file_unfurl_url,
app_unfurl_url=sample_file_unfurl_url,
external_ref=ExternalRef(id="sample_task_id"),
entity_payload=EntityPayload(
attributes=EntityAttributes(
full_size_preview=EntityFullSizePreview(is_supported=True, preview_url="https://muertoscoffeeco.com/cdn/shop/articles/Why-Roast-Coffee-scaled_1000x.jpg?v=1592234291"),
title=EntityTitle(
text="My File",
edit=EntityEditSupport(enabled=True),
)
),
fields=TaskEntityFields(
description=EntityStringField(
value="Hello World!",
edit=EntityEditSupport(enabled=True)
)
),
custom_fields=[
EntityCustomField(
key="hello-world",
label="hello-world",
value="hello-world",
type="string",
)
],
),
)
}
6 changes: 6 additions & 0 deletions messaging/work-objects/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mypy==1.18.2
pytest==9.0.1
ruff==0.14.5
slack_sdk>=3.39.0
slack-bolt
slack-cli-hooks<1.0.0
Empty file.