├── .devcontainer ├── Dockerfile ├── README.md └── devcontainer.json ├── .github ├── dependabot.yml └── workflows │ └── deno.yml ├── .gitignore ├── .slack ├── .gitignore ├── apps.json └── hooks.json ├── .vscode └── settings.json ├── Block_Kit_Modals ├── README.md ├── functions │ └── demo.ts ├── triggers │ └── link.ts └── workflows │ └── block_kit_modal_demo.ts ├── Built-in_Forms ├── README.md ├── triggers │ └── link.ts └── workflows │ └── form_demo.ts ├── Button_Interactions ├── README.md ├── functions │ ├── handle_interactive_blocks.ts │ └── send_block_kit_message.ts ├── triggers │ ├── block_kit_button_link.ts │ └── interactive_blocks_link.ts └── workflows │ ├── block_kit_button_demo.ts │ └── interactive_blocks_demo.ts ├── Canvases ├── README.md ├── triggers │ ├── canvas_copy_link.ts │ ├── canvas_create_link.ts │ ├── canvas_share_link.ts │ └── canvas_update_link.ts └── workflows │ ├── copy_canvas.ts │ ├── create_canvas.ts │ ├── share_canvas.ts │ └── update_canvas.ts ├── Connectors ├── README.md ├── triggers │ ├── giphy.ts │ └── google_calendar.ts └── workflows │ ├── giphy.ts │ └── google_calendar.ts ├── Custom_Functions ├── README.md ├── functions │ ├── my_send_message.ts │ └── my_send_message_test.ts ├── triggers │ └── link.ts └── workflows │ └── my_send_message_workflow.ts ├── Datastores ├── README.md ├── datastores │ ├── pto.ts │ └── tasks.ts ├── functions │ ├── pto_demo.ts │ └── tasks_demo.ts ├── triggers │ ├── pto_link.ts │ └── task_link.ts └── workflows │ ├── pto.ts │ └── task_manager.ts ├── Event_Triggers ├── README.md ├── triggers │ ├── channel_created.ts │ ├── messages_posted.ts │ └── reaction_added.ts └── workflows │ ├── message_to_channel_creator.ts │ ├── ping_pong_message.ts │ └── reply_to_reaction.ts ├── External_API_Calls ├── README.md ├── functions │ ├── httpbin_get.ts │ └── httpbin_get_test.ts ├── triggers │ └── link.ts └── workflows │ └── ephemeral_message.ts ├── LICENSE ├── Messaging ├── README.md ├── triggers │ ├── channel_message_link.ts │ ├── channel_message_webhook.ts │ ├── direct_message_link.ts │ └── ephemeral_message_link.ts └── workflows │ ├── channel_message.ts │ ├── direct_message.ts │ └── ephemeral_message.ts ├── README.md ├── Scheduled_Triggers ├── README.md ├── triggers │ └── scheduled_only_once.ts └── workflows │ └── do_nothing.ts ├── assets └── default_new_app_icon.png ├── deno.jsonc └── manifest.ts /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the Ubtuntu base image 2 | FROM ubuntu:22.04 3 | 4 | # Download development tools 5 | RUN apt-get update 6 | RUN apt-get install -y curl 7 | RUN apt-get install -y git 8 | RUN apt-get install -y unzip 9 | 10 | # Run the container as a user 11 | RUN useradd -m -s /bin/bash slackdev 12 | RUN chown slackdev /usr/local/bin 13 | USER slackdev 14 | 15 | # Install the project runtime 16 | RUN curl -fsSL https://deno.land/install.sh | sh 17 | RUN export DENO_INSTALL="/home/slackdev/.deno" 18 | RUN export PATH="$DENO_INSTALL/bin:$PATH" 19 | 20 | # Set the working directory 21 | WORKDIR /workspaces 22 | 23 | # Install the Slack CLI 24 | RUN curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash -s -- -d 25 | -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # .devcontainer 2 | 3 | A [development container][container] provides a predefined environment with 4 | some tools needed for development, which can be useful in editors such as 5 | [Visual Studio Code][vscode] or remote settings like [Codespaces][codespaces]. 6 | 7 | This specific container packages [the Slack CLI][cli] with the project runtime 8 | and a few development tools. The `Dockerfile` details the container. 9 | 10 | ## Editor extensions 11 | 12 | Modifications to an editor can be made with changes to the `devcontainer.json` 13 | file: 14 | 15 | ```diff 16 | { 17 | "customizations": { 18 | "vscode": { 19 | "extensions": [ 20 | + "GitHub.copilot", 21 | "denoland.vscode-deno", 22 | "ms-azuretools.vscode-docker" 23 | ], 24 | + "settings": { 25 | + "terminal.integrated.defaultProfile.linux": "zsh" 26 | + } 27 | } 28 | } 29 | } 30 | ``` 31 | 32 | [codespaces]: https://github.com/features/codespaces 33 | [cli]: https://api.slack.com/automation/cli 34 | [container]: https://containers.dev/ 35 | [vscode]: https://code.visualstudio.com/docs/devcontainers/containers 36 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Slack Platform", 3 | "dockerFile": "Dockerfile", 4 | "remoteUser": "slackdev", 5 | "customizations": { 6 | "vscode": { 7 | "extensions": [ 8 | "denoland.vscode-deno", 9 | "ms-azuretools.vscode-docker" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/deno.yml: -------------------------------------------------------------------------------- 1 | name: Deno app build and testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | deno: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | 16 | steps: 17 | - name: Setup repo 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Deno 21 | uses: denoland/setup-deno@v2 22 | with: 23 | deno-version: v2.x 24 | 25 | - name: Verify formatting 26 | run: deno fmt --check 27 | 28 | - name: Run linter 29 | run: deno lint 30 | 31 | - name: Run tests 32 | run: deno task test 33 | 34 | - name: Run type check 35 | run: deno check *.ts && deno check **/**/*.ts 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | package 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.slack/.gitignore: -------------------------------------------------------------------------------- 1 | apps.dev.json 2 | cache/ 3 | -------------------------------------------------------------------------------- /.slack/apps.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.slack/hooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "get-hooks": "deno run -q --allow-read --allow-net https://deno.land/x/deno_slack_hooks@1.3.2/mod.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.suggest.imports.hosts": { 5 | "https://deno.land": false 6 | }, 7 | "[typescript]": { 8 | "editor.formatOnSave": true, 9 | "editor.defaultFormatter": "denoland.vscode-deno" 10 | }, 11 | "editor.tabSize": 2 12 | } 13 | -------------------------------------------------------------------------------- /Block_Kit_Modals/README.md: -------------------------------------------------------------------------------- 1 | # Block Kit Modals 2 | 3 | This sub-app guides you on how to use Block Kit modals in your custom function. 4 | If you haven't set up the Slack CLI and the project on your local machine yet, 5 | visit [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **Block Kit Modal Demo Workflow:** Demonstrate how to handle Block Kit modal 10 | interactions 11 | 12 | ## Block Kit Modal Demo Workflow 13 | 14 | In this example workflow, you will create a link trigger, click the link to 15 | start the workflow, and see how the modal interactions work. 16 | 17 | ### Create a Link Trigger 18 | 19 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 20 | to run. These triggers can be invoked by a user or automatically as a response 21 | to an event within Slack. 22 | 23 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 24 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 25 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 26 | associated workflow. 27 | 28 | Link triggers are _unique to each installed version of your app_. This means 29 | that Shortcut URLs will be different across each workspace, as well as between 30 | [locally run](#running-your-project-locally) and 31 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 32 | the workspace that you'd like to create the trigger in. Each workspace has a 33 | development version (denoted by `(local)`), as well as a deployed version. 34 | 35 | To create a link trigger for the workflow in this template, run the following 36 | command: 37 | 38 | ```zsh 39 | $ slack trigger create --trigger-def ./Block_Kit_Modals/triggers/link.ts 40 | ``` 41 | 42 | After selecting a Workspace, the trigger should be created successfully. 43 | 44 | After selecting a Workspace, the output provided will include the link trigger 45 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 46 | a bookmark in a channel of the workspace you selected. 47 | 48 | ## Running Your Project Locally 49 | 50 | While building your app, you can see your changes propagated to your workspace 51 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 52 | is the development version if the name has the string `(local)` appended. 53 | 54 | ```zsh 55 | # Run app locally 56 | $ slack run 57 | 58 | Connected, awaiting events 59 | ``` 60 | 61 | Once running, click the 62 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 63 | `(local)` version of your app. This should start the included sample workflow. 64 | 65 | To stop running locally, press ` + C` to end the process. 66 | 67 | ## Manual Testing 68 | 69 | Once you click the link trigger in a channel, the trigger starts the 70 | `Block_Kit_Modals/workflows/block_kit_modal_demo.ts` workflow, which opens a 71 | modal dialog for you. 72 | 73 | When it's successful, you will see a modal dialog that consists of two pages. 74 | The first page has a input length validation. You can do the same as long as 75 | your `ViewSubmissionHandler` can respond within 3 seconds. 76 | 77 | ## Deploying Your App 78 | 79 | Once you're done with development, you can deploy the production version of your 80 | app to Slack hosting using `slack deploy`: 81 | 82 | ```zsh 83 | $ slack deploy 84 | ``` 85 | 86 | After deploying, create a trigger for the production version of your app (not 87 | appended with `(local)`). Once the trigger is invoked, the workflow should run 88 | just as it did when developing locally. 89 | 90 | ## Project Structure 91 | 92 | ### `manifest.ts` 93 | 94 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 95 | configuration. This file defines attributes like app name and description. 96 | 97 | ### `.slack/hooks.json` 98 | 99 | Used by the CLI to interact with the project's SDK dependencies. It contains 100 | script hooks that are executed by the CLI and implemented by the SDK. 101 | 102 | ### `Block_Kit_Modals/functions` 103 | 104 | [Functions](https://api.slack.com/automation/functions) are reusable building 105 | blocks of automation that accept inputs, perform calculations, and provide 106 | outputs. Functions can be used independently or as steps in workflows. 107 | 108 | ### `Block_Kit_Modals/workflows` 109 | 110 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 111 | are executed in order. Each step in a workflow is a function. 112 | 113 | Workflows can be configured to run without user input, or they can collect 114 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 115 | continuing to the next step. 116 | 117 | ### `Block_Kit_Modals/triggers` 118 | 119 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 120 | are executed. A trigger file describes a scenario in which a workflow should be 121 | run, such as a user pressing a button or when a specific event occurs. 122 | 123 | ## What's Next? 124 | 125 | To learn more about other samples, visit [the top-level guide](../README.md) to 126 | find more! 127 | -------------------------------------------------------------------------------- /Block_Kit_Modals/functions/demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | export const def = DefineFunction({ 4 | callback_id: "block-kit-modal-demo", 5 | title: "Block Kit modal demo", 6 | source_file: "Block_Kit_Modals/functions/demo.ts", 7 | input_parameters: { 8 | properties: { interactivity: { type: Schema.slack.types.interactivity } }, 9 | required: ["interactivity"], 10 | }, 11 | output_parameters: { properties: {}, required: [] }, 12 | }); 13 | 14 | export default SlackFunction( 15 | def, 16 | // --------------------------- 17 | // The first handler function that opens a modal. 18 | // This function can be called when the workflow executes the function step. 19 | // --------------------------- 20 | async ({ inputs, client }) => { 21 | // Open a new modal with the end-user who interacted with the link trigger 22 | const response = await client.views.open({ 23 | interactivity_pointer: inputs.interactivity.interactivity_pointer, 24 | view: { 25 | "type": "modal", 26 | // Note that this ID can be used for dispatching view submissions and view closed events. 27 | "callback_id": "first-page", 28 | // This option is required to be notified when this modal is closed by the user 29 | "notify_on_close": true, 30 | "title": { "type": "plain_text", "text": "My App" }, 31 | "submit": { "type": "plain_text", "text": "Next" }, 32 | "close": { "type": "plain_text", "text": "Close" }, 33 | "blocks": [ 34 | { 35 | "type": "input", 36 | "block_id": "first_text", 37 | "element": { "type": "plain_text_input", "action_id": "action" }, 38 | "label": { "type": "plain_text", "text": "First" }, 39 | }, 40 | ], 41 | }, 42 | }); 43 | if (response.error) { 44 | const error = 45 | `Failed to open a modal in the demo workflow. Contact the app maintainers with the following information - (error: ${response.error})`; 46 | return { error }; 47 | } 48 | return { 49 | // To continue with this interaction, return false for the completion 50 | completed: false, 51 | }; 52 | }, 53 | ) 54 | // --------------------------- 55 | // The handler that can be called when the above modal data is submitted. 56 | // It saves the inputs from the first page as private_metadata, 57 | // and then displays the second-page modal view. 58 | // --------------------------- 59 | .addViewSubmissionHandler(["first-page"], ({ view }) => { 60 | // Extract the input values from the view data 61 | const firstText = view.state.values.first_text.action.value; 62 | // Input validations 63 | if (firstText.length < 20) { 64 | return { 65 | response_action: "errors", 66 | // The key must be a valid block_id in the blocks on a modal 67 | errors: { first_text: "Must be 20 characters or longer" }, 68 | }; 69 | } 70 | // Successful. Update the modal with the second page presentation 71 | return { 72 | response_action: "update", 73 | view: { 74 | "type": "modal", 75 | "callback_id": "second-page", 76 | // This option is required to be notified when this modal is closed by the user 77 | "notify_on_close": true, 78 | "title": { "type": "plain_text", "text": "My App" }, 79 | "submit": { "type": "plain_text", "text": "Next" }, 80 | "close": { "type": "plain_text", "text": "Close" }, 81 | // Hidden string data, which is not visible to end-users 82 | // You can use this property to transfer the state of interaction 83 | // to the following event handlers. 84 | // (Up to 3,000 characters allowed) 85 | "private_metadata": JSON.stringify({ firstText }), 86 | "blocks": [ 87 | // Display the inputs from "first-page" modal view 88 | { 89 | "type": "section", 90 | "text": { "type": "mrkdwn", "text": `First: ${firstText}` }, 91 | }, 92 | // New input block to receive text 93 | { 94 | "type": "input", 95 | "block_id": "second_text", 96 | "element": { "type": "plain_text_input", "action_id": "action" }, 97 | "label": { "type": "plain_text", "text": "Second" }, 98 | }, 99 | ], 100 | }, 101 | }; 102 | }) 103 | // --------------------------- 104 | // The handler that can be called when the second modal data is submitted. 105 | // It displays the completion page view with the inputs from 106 | // the first and second pages. 107 | // --------------------------- 108 | .addViewSubmissionHandler(["second-page"], ({ view }) => { 109 | // Extract the first-page inputs from private_metadata 110 | const { firstText } = JSON.parse(view.private_metadata!); 111 | // Extract the second-page inputs from the view data 112 | const secondText = view.state.values.second_text.action.value; 113 | // Displays the third page, which tells the completion of the interaction 114 | return { 115 | response_action: "update", 116 | view: { 117 | "type": "modal", 118 | "callback_id": "completion", 119 | // This option is required to be notified when this modal is closed by the user 120 | "notify_on_close": true, 121 | "title": { "type": "plain_text", "text": "My App" }, 122 | // This modal no longer accepts further inputs. 123 | // So, the "Submit" button is intentionally removed from the view. 124 | "close": { "type": "plain_text", "text": "Close" }, 125 | // Display the two inputs 126 | "blocks": [ 127 | { 128 | "type": "section", 129 | "text": { "type": "mrkdwn", "text": `First: ${firstText}` }, 130 | }, 131 | { 132 | "type": "section", 133 | "text": { "type": "mrkdwn", "text": `Second: ${secondText}` }, 134 | }, 135 | ], 136 | }, 137 | }; 138 | }) 139 | // --------------------------- 140 | // The handler that can be called when the second modal data is closed. 141 | // If your app runs some resource-intensive operations on the backend side, 142 | // you can cancel the ongoing process and/or tell the end-user 143 | // what to do next in DM and so on. 144 | // --------------------------- 145 | .addViewClosedHandler( 146 | ["first-page", "second-page", "completion"], 147 | async ({ body, client, view }) => { 148 | console.log(`view_closed handler called: ${JSON.stringify(view)}`); 149 | await client.functions.completeSuccess({ 150 | function_execution_id: body.function_data.execution_id, 151 | outputs: {}, 152 | }); 153 | }, 154 | ); 155 | -------------------------------------------------------------------------------- /Block_Kit_Modals/triggers/link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import workflow from "../workflows/block_kit_modal_demo.ts"; 3 | 4 | /** 5 | * This trigger starts the workflow when an end-user clicks the link. 6 | * Learn more at https://api.slack.com/automation/triggers/link 7 | */ 8 | const trigger: Trigger = { 9 | type: "shortcut", 10 | name: "Block Kit Modal Demo Trigger", 11 | workflow: `#/workflows/${workflow.definition.callback_id}`, 12 | inputs: { 13 | // interactivity is necessary for opening a modal 14 | interactivity: { value: "{{data.interactivity}}" }, 15 | }, 16 | }; 17 | 18 | // Note that the Trigger object must be default-exported 19 | export default trigger; 20 | -------------------------------------------------------------------------------- /Block_Kit_Modals/workflows/block_kit_modal_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as Demo } from "../functions/demo.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "block-kit-modal-demo-workflow", 6 | title: "Block Kit Modal Demo Workflow", 7 | input_parameters: { 8 | properties: { interactivity: { type: Schema.slack.types.interactivity } }, 9 | required: ["interactivity"], 10 | }, 11 | }); 12 | 13 | workflow.addStep(Demo, { interactivity: workflow.inputs.interactivity }); 14 | 15 | export default workflow; 16 | -------------------------------------------------------------------------------- /Built-in_Forms/README.md: -------------------------------------------------------------------------------- 1 | # Built-in Forms 2 | 3 | This sub-app guides you on how to use the buit-in modal form. If you haven't set 4 | up the Slack CLI and the project on your local machine yet, visit 5 | [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **Form Demo Workflow:** Demonstrate all the available input in the built-in 10 | forms 11 | 12 | ## Form Demo Workflow 13 | 14 | In this example workflow, you will create a link trigger, click the link to 15 | start the workflow, submit the modal data, and then see the outputs in an 16 | ephemeral message. 17 | 18 | ### Create a Link Trigger 19 | 20 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 21 | to run. These triggers can be invoked by a user or automatically as a response 22 | to an event within Slack. 23 | 24 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 25 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 26 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 27 | associated workflow. 28 | 29 | Link triggers are _unique to each installed version of your app_. This means 30 | that Shortcut URLs will be different across each workspace, as well as between 31 | [locally run](#running-your-project-locally) and 32 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 33 | the workspace that you'd like to create the trigger in. Each workspace has a 34 | development version (denoted by `(local)`), as well as a deployed version. 35 | 36 | To create a link trigger for the workflow in this template, run the following 37 | command: 38 | 39 | ```zsh 40 | $ slack trigger create --trigger-def ./Built-in_Forms/triggers/link.ts 41 | ``` 42 | 43 | After selecting a Workspace, the trigger should be created successfully. 44 | 45 | After selecting a Workspace, the output provided will include the link trigger 46 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 47 | a bookmark in a channel of the workspace you selected. 48 | 49 | ## Running Your Project Locally 50 | 51 | While building your app, you can see your changes propagated to your workspace 52 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 53 | is the development version if the name has the string `(local)` appended. 54 | 55 | ```zsh 56 | # Run app locally 57 | $ slack run 58 | 59 | Connected, awaiting events 60 | ``` 61 | 62 | Once running, click the 63 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 64 | `(local)` version of your app. This should start the included sample workflow. 65 | 66 | To stop running locally, press ` + C` to end the process. 67 | 68 | ## Manual Testing 69 | 70 | Once you click the link trigger in a channel, the trigger starts the 71 | `Built-in_Forms/workflows/form_demo.ts` workflow, which runs the built-in 72 | `OpenForm` function. 73 | 74 | When it's successful, you will see a modal dialog with lots of input fields. 75 | Once you submit it, you will receive an ephemeral message that displays all the 76 | inputs. 77 | 78 | ## Deploying Your App 79 | 80 | Once you're done with development, you can deploy the production version of your 81 | app to Slack hosting using `slack deploy`: 82 | 83 | ```zsh 84 | $ slack deploy 85 | ``` 86 | 87 | After deploying, create a trigger for the production version of your app (not 88 | appended with `(local)`). Once the trigger is invoked, the workflow should run 89 | just as it did when developing locally. 90 | 91 | ## Project Structure 92 | 93 | ### `manifest.ts` 94 | 95 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 96 | configuration. This file defines attributes like app name and description. 97 | 98 | ### `.slack/hooks.json` 99 | 100 | Used by the CLI to interact with the project's SDK dependencies. It contains 101 | script hooks that are executed by the CLI and implemented by the SDK. 102 | 103 | ### `Built-in_Forms/functions` 104 | 105 | [Functions](https://api.slack.com/automation/functions) are reusable building 106 | blocks of automation that accept inputs, perform calculations, and provide 107 | outputs. Functions can be used independently or as steps in workflows. 108 | 109 | ### `Built-in_Forms/workflows` 110 | 111 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 112 | are executed in order. Each step in a workflow is a function. 113 | 114 | Workflows can be configured to run without user input, or they can collect 115 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 116 | continuing to the next step. 117 | 118 | ### `Built-in_Forms/triggers` 119 | 120 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 121 | are executed. A trigger file describes a scenario in which a workflow should be 122 | run, such as a user pressing a button or when a specific event occurs. 123 | 124 | ## What's Next? 125 | 126 | To learn more about other samples, visit [the top-level guide](../README.md) to 127 | find more! 128 | -------------------------------------------------------------------------------- /Built-in_Forms/triggers/link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/form_demo.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Form Demo Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // interactivity is necessary for using OpenForm function 15 | interactivity: { value: TriggerContextData.Shortcut.interactivity }, 16 | // The following inputs are not necessary for OpenForm 17 | // You'll use this just for the succeeding functions, 18 | // which confirm the outputs of OpenForm 19 | user_id: { value: TriggerContextData.Shortcut.user_id }, 20 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 21 | }, 22 | }; 23 | 24 | // Note that the Trigger object must be default-exported 25 | export default trigger; 26 | -------------------------------------------------------------------------------- /Built-in_Forms/workflows/form_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | const workflow = DefineWorkflow({ 4 | callback_id: "form-demo-workflow", 5 | title: "OpenForm Demo Workflow", 6 | input_parameters: { 7 | properties: { 8 | interactivity: { type: Schema.slack.types.interactivity }, 9 | user_id: { type: Schema.slack.types.user_id }, 10 | channel_id: { type: Schema.slack.types.channel_id }, 11 | }, 12 | required: ["interactivity", "user_id", "channel_id"], 13 | }, 14 | }); 15 | 16 | // Step using the built-in form 17 | const formStep = workflow.addStep(Schema.slack.functions.OpenForm, { 18 | title: "Send a greeting", 19 | interactivity: workflow.inputs.interactivity, 20 | submit_label: "Send greeting", 21 | fields: { 22 | // fields.elements will be converted to Block Kit components under the hood 23 | // To learn more on Block Kit, visit https://api.slack.com/block-kit 24 | elements: [ 25 | { 26 | name: "recipient", 27 | title: "Recipient", 28 | type: Schema.slack.types.user_id, // => "users_select" 29 | default: workflow.inputs.user_id, 30 | }, 31 | { 32 | name: "channel", 33 | title: "Channel to send message to", 34 | type: Schema.slack.types.channel_id, // => "channels_select" 35 | default: workflow.inputs.channel_id, 36 | }, 37 | { 38 | name: "message", 39 | title: "Message to recipient", 40 | type: Schema.types.string, // => "plain_text_input" 41 | long: true, // => multiline: true 42 | minLength: 1, // inclusive 43 | maxLength: 100, // inclusive 44 | }, 45 | { 46 | name: "favorite_animal", 47 | title: "Favorite Animal", 48 | type: Schema.types.string, // => "static_select" 49 | choices: [ 50 | { value: "dog", title: "Dog" }, 51 | { value: "cat", title: "Cat" }, 52 | ], 53 | enum: ["dog", "cat"], 54 | default: "cat", 55 | }, 56 | { 57 | name: "favorite_animals", 58 | title: "Favorite Animals", 59 | type: Schema.types.array, // => "mutli_static_select" 60 | items: { 61 | type: Schema.types.string, 62 | choices: [ 63 | { value: "dog", title: "Dog" }, 64 | { value: "cat", title: "Cat" }, 65 | ], 66 | enum: ["dog", "cat"], 67 | }, 68 | maxItems: 2, 69 | default: ["cat"], 70 | }, 71 | { 72 | name: "contact", 73 | title: "Your Email Address", 74 | type: Schema.types.string, 75 | format: "email", // => "email_text_input" 76 | }, 77 | { 78 | name: "channels", 79 | title: "Favorite Channels", 80 | type: Schema.types.array, // => "multi_channels_select" 81 | items: { type: Schema.slack.types.channel_id }, 82 | }, 83 | { 84 | name: "team_members", 85 | title: "Team Members", 86 | type: Schema.types.array, // => "multi_users_select" 87 | items: { type: Schema.slack.types.user_id }, 88 | }, 89 | { 90 | name: "approved", 91 | title: "Already Approved by My Manager", 92 | type: Schema.types.boolean, // => "checkboxes" 93 | }, 94 | { 95 | name: "count", 96 | title: "Count", 97 | type: Schema.types.integer, // => "number_input" with is_decimal_allowed: false 98 | }, 99 | { 100 | name: "amount", 101 | title: "Amount", 102 | type: Schema.types.number, // => "number_input" 103 | }, 104 | { 105 | name: "due_date", 106 | title: "Due Date", 107 | type: Schema.slack.types.date, // => "datepicker" 108 | }, 109 | { 110 | name: "due_time", 111 | title: "Due Date Time", 112 | type: Schema.slack.types.timestamp, // => "datetimepicker" 113 | }, 114 | { 115 | name: "rich_text", 116 | title: "Rich Text Input", 117 | type: Schema.slack.types.rich_text, 118 | }, 119 | ], 120 | required: ["recipient", "channel", "message"], 121 | }, 122 | }); 123 | 124 | // Confirm the outputs from the above OpenForm function 125 | workflow.addStep(Schema.slack.functions.SendEphemeralMessage, { 126 | // The name of the element will be the key to access the value 127 | user_id: formStep.outputs.fields.recipient, 128 | channel_id: formStep.outputs.fields.channel, 129 | message: "OpenForm's `outputs.fields`: ```" + 130 | formStep.outputs.fields + 131 | "```", 132 | }); 133 | 134 | export default workflow; 135 | -------------------------------------------------------------------------------- /Button_Interactions/README.md: -------------------------------------------------------------------------------- 1 | # Button Interactions 2 | 3 | This sub-app guides you on how to handle button interactions in a channel 4 | message. If you haven't set up the Slack CLI and the project on your local 5 | machine yet, visit [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **Interactive Blocks Demo Workflow:** Demonstrate how to handle the built-in 10 | `SendMessage`'s `interactive_blocks` 11 | - **Block Kit Button Demo Workflow:** Demonstrate how to handle Block Kit's 12 | button blocks 13 | 14 | ## Interactive Blocks Demo Workflow 15 | 16 | In this example workflow, you will create a link trigger, click the link to 17 | start the workflow, and see how the interactions with `interactive_blocks` work. 18 | 19 | ### Create a Link Trigger 20 | 21 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 22 | to run. These triggers can be invoked by a user or automatically as a response 23 | to an event within Slack. 24 | 25 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 26 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 27 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 28 | associated workflow. 29 | 30 | Link triggers are _unique to each installed version of your app_. This means 31 | that Shortcut URLs will be different across each workspace, as well as between 32 | [locally run](#running-your-project-locally) and 33 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 34 | the workspace that you'd like to create the trigger in. Each workspace has a 35 | development version (denoted by `(local)`), as well as a deployed version. 36 | 37 | To create a link trigger for the workflow in this template, run the following 38 | command: 39 | 40 | ```zsh 41 | $ slack trigger create --trigger-def ./Button_Interactions/triggers/interactive_blocks_link.ts 42 | ``` 43 | 44 | After selecting a Workspace, the trigger should be created successfully. 45 | 46 | After selecting a Workspace, the output provided will include the link trigger 47 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 48 | a bookmark in a channel of the workspace you selected. 49 | 50 | ## Running Your Project Locally 51 | 52 | While building your app, you can see your changes propagated to your workspace 53 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 54 | is the development version if the name has the string `(local)` appended. 55 | 56 | ```zsh 57 | # Run app locally 58 | $ slack run 59 | 60 | Connected, awaiting events 61 | ``` 62 | 63 | Once running, click the 64 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 65 | `(local)` version of your app. This should start the included sample workflow. 66 | 67 | To stop running locally, press ` + C` to end the process. 68 | 69 | ## Manual Testing 70 | 71 | Once you click the link trigger in a channel, the trigger starts the 72 | `Button_Interactions/workflows/interactive_blocks_demo.ts` workflow, which opens 73 | a modal dialog for you. 74 | 75 | When it's successful, you will see a message with two buttons. When you click 76 | one of them, `Button_Interactions/functions/handle_interactive_blocks.ts` 77 | function will be executed. 78 | 79 | You can handle the click event data. However, it's not feasible to customize the 80 | message UI with `SendMessage`'s `interactive_blocks`. When you need to go 81 | further, you can directly use Block Kit in your `chat.postMessage` API payload. 82 | 83 | ## Block Kit Button Demo Workflow 84 | 85 | To create a link trigger for the Block Kit based workflow in this template, run 86 | the following command: 87 | 88 | ```zsh 89 | $ slack trigger create --trigger-def ./Button_Interactions/triggers/block_kit_button_link.ts 90 | ``` 91 | 92 | You can run the workflow the same way as the above `interactive_blocks` one. The 93 | workflow behaves mostly the same, but the key difference is that message 94 | modification when clicking a button. You can customize the UI and its behaviors 95 | as you want. 96 | 97 | ## Deploying Your App 98 | 99 | Once you're done with development, you can deploy the production version of your 100 | app to Slack hosting using `slack deploy`: 101 | 102 | ```zsh 103 | $ slack deploy 104 | ``` 105 | 106 | After deploying, create a trigger for the production version of your app (not 107 | appended with `(local)`). Once the trigger is invoked, the workflow should run 108 | just as it did when developing locally. 109 | 110 | ## Project Structure 111 | 112 | ### `manifest.ts` 113 | 114 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 115 | configuration. This file defines attributes like app name and description. 116 | 117 | ### `.slack/hooks.json` 118 | 119 | Used by the CLI to interact with the project's SDK dependencies. It contains 120 | script hooks that are executed by the CLI and implemented by the SDK. 121 | 122 | ### `Button_Interactions/functions` 123 | 124 | [Functions](https://api.slack.com/automation/functions) are reusable building 125 | blocks of automation that accept inputs, perform calculations, and provide 126 | outputs. Functions can be used independently or as steps in workflows. 127 | 128 | ### `Button_Interactions/workflows` 129 | 130 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 131 | are executed in order. Each step in a workflow is a function. 132 | 133 | Workflows can be configured to run without user input, or they can collect 134 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 135 | continuing to the next step. 136 | 137 | ### `Button_Interactions/triggers` 138 | 139 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 140 | are executed. A trigger file describes a scenario in which a workflow should be 141 | run, such as a user pressing a button or when a specific event occurs. 142 | 143 | ## What's Next? 144 | 145 | To learn more about other samples, visit [the top-level guide](../README.md) to 146 | find more! 147 | -------------------------------------------------------------------------------- /Button_Interactions/functions/handle_interactive_blocks.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | export const def = DefineFunction({ 4 | callback_id: "handle-interactive-blocks", 5 | title: "Handle button clicks in interactive_blocks", 6 | source_file: "Button_Interactions/functions/handle_interactive_blocks.ts", 7 | input_parameters: { 8 | // The input values from the SendMessage function's interactive_blocks 9 | properties: { 10 | action: { type: Schema.types.object }, 11 | interactivity: { type: Schema.slack.types.interactivity }, 12 | messageLink: { type: Schema.types.string }, 13 | messageTs: { type: Schema.types.string }, 14 | }, 15 | required: ["action", "interactivity"], 16 | }, 17 | output_parameters: { properties: {}, required: [] }, 18 | }); 19 | 20 | export default SlackFunction( 21 | def, 22 | // When the workflow is executed, this handler is called 23 | async ({ inputs, client }) => { 24 | if (inputs.action.action_id === "deny") { 25 | // Only when the click is on "Deny", this function opens a modal 26 | // to ask the reason of the denial 27 | const response = await client.views.open({ 28 | interactivity_pointer: inputs.interactivity.interactivity_pointer, 29 | view: buildNewModalView(), 30 | }); 31 | if (response.error) { 32 | const error = `Failed to open a modal due to ${response.error}`; 33 | return { error }; 34 | } 35 | // Continue the interactions on the modal 36 | return { completed: false }; 37 | } 38 | return { completed: true, outputs: {} }; 39 | }, 40 | ) 41 | // Handle the button click events on the modal 42 | .addBlockActionsHandler("clear-inputs", async ({ body, client }) => { 43 | const response = await client.views.update({ 44 | interactivity_pointer: body.interactivity.interactivity_pointer, 45 | view_id: body.view.id, 46 | view: buildNewModalView(), 47 | }); 48 | if (response.error) { 49 | const error = `Failed to update a modal due to ${response.error}`; 50 | return { error }; 51 | } 52 | return { completed: false }; 53 | }) 54 | // Handle the data submission from the modal 55 | .addViewSubmissionHandler( 56 | ["deny-reason-submission"], 57 | ({ view }) => { 58 | const values = view.state.values; 59 | const reason = String(Object.values(values)[0]["deny-reason"].value); 60 | if (reason.length <= 5) { 61 | console.log(reason); 62 | const errors: Record = {}; 63 | const blockId = Object.keys(values)[0]; 64 | errors[blockId] = "The reason must be 5 characters or longer"; 65 | return { response_action: "errors", errors }; 66 | } 67 | return {}; 68 | }, 69 | ) 70 | // Handle the events when the end-user closes the modal 71 | .addViewClosedHandler( 72 | ["deny-reason-submission", "deny-reason-confirmation"], 73 | ({ view }) => { 74 | console.log(JSON.stringify(view, null, 2)); 75 | }, 76 | ); 77 | 78 | /** 79 | * Returns the initial state of the modal view 80 | * @returns the initial modal view 81 | */ 82 | function buildNewModalView() { 83 | return { 84 | "type": "modal", 85 | "callback_id": "deny-reason-submission", 86 | "title": { "type": "plain_text", "text": "Reason for the denial" }, 87 | "notify_on_close": true, 88 | "submit": { "type": "plain_text", "text": "Confirm" }, 89 | "blocks": [ 90 | { 91 | "type": "input", 92 | // If you reuse block_id when refreshing an existing modal view, 93 | // the old block may remain. To avoid this, always set a random value. 94 | "block_id": crypto.randomUUID(), 95 | "label": { "type": "plain_text", "text": "Reason" }, 96 | "element": { 97 | "type": "plain_text_input", 98 | "action_id": "deny-reason", 99 | "multiline": true, 100 | "placeholder": { 101 | "type": "plain_text", 102 | "text": "Share the reason why you denied the request in detail", 103 | }, 104 | }, 105 | }, 106 | { 107 | "type": "actions", 108 | "block_id": "clear", 109 | "elements": [ 110 | { 111 | type: "button", 112 | action_id: "clear-inputs", 113 | text: { type: "plain_text", text: "Clear all the inputs" }, 114 | style: "danger", 115 | }, 116 | ], 117 | }, 118 | ], 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /Button_Interactions/functions/send_block_kit_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | export const def = DefineFunction({ 4 | callback_id: "send_interactive_message", 5 | title: "Send a message with interactive blocks", 6 | source_file: "Button_Interactions/functions/send_block_kit_message.ts", 7 | input_parameters: { 8 | properties: { 9 | user_id: { type: Schema.slack.types.user_id }, 10 | channel_id: { type: Schema.slack.types.channel_id }, 11 | }, 12 | required: ["user_id", "channel_id"], 13 | }, 14 | output_parameters: { properties: {}, required: [] }, 15 | }); 16 | 17 | export default SlackFunction( 18 | def, 19 | // When the workflow is executed, this handler is called 20 | async ({ inputs, client }) => { 21 | const text = `Do you approve <@${inputs.user_id}>'s time off request?`; 22 | // Block Kit elements (https://api.slack.com/block-kit) 23 | const blocks = [ 24 | { 25 | type: "section", 26 | text: { type: "mrkdwn", text }, 27 | }, 28 | { type: "divider" }, 29 | { 30 | type: "actions", 31 | block_id: "approve-deny-buttons", 32 | elements: [ 33 | { 34 | type: "button", 35 | action_id: "approve", 36 | text: { type: "plain_text", text: "Approve" }, 37 | style: "primary", 38 | }, 39 | { 40 | type: "button", 41 | action_id: "deny", 42 | text: { type: "plain_text", text: "Deny" }, 43 | style: "danger", 44 | }, 45 | ], 46 | }, 47 | ]; 48 | const response = await client.chat.postMessage({ 49 | channel: inputs.channel_id, 50 | text, 51 | blocks, 52 | }); 53 | if (response.error) { 54 | console.log(JSON.stringify(response, null, 2)); 55 | const error = `Failed to post a message due to ${response.error}`; 56 | return { error }; 57 | } 58 | // To continue with this interaction, return false for the completion 59 | return { completed: false }; 60 | }, 61 | ) 62 | // Handle the "Approve" button clicks 63 | .addBlockActionsHandler("approve", async ({ body, client, inputs }) => { 64 | const text = "Thank you for approving the request!"; 65 | const response = await client.chat.update({ 66 | channel: inputs.channel_id, 67 | ts: body.container.message_ts, 68 | text, 69 | blocks: [{ type: "section", text: { type: "mrkdwn", text } }], 70 | }); 71 | if (response.error) { 72 | const error = `Failed to update the message due to ${response.error}`; 73 | return { error }; 74 | } 75 | return { completed: true, outputs: {} }; 76 | }) 77 | // Handle the "Deny" button clicks 78 | .addBlockActionsHandler("deny", async ({ body, client, inputs }) => { 79 | const text = 80 | "OK, we need more information... Could you share the reason for denial?"; 81 | const messageResponse = await client.chat.update({ 82 | channel: inputs.channel_id, 83 | ts: body.container.message_ts, 84 | text, 85 | blocks: [{ type: "section", text: { type: "mrkdwn", text } }], 86 | }); 87 | if (messageResponse.error) { 88 | const error = 89 | `Failed to update the message due to ${messageResponse.error}`; 90 | return { error }; 91 | } 92 | const modalResponse = await client.views.open({ 93 | interactivity_pointer: body.interactivity.interactivity_pointer, 94 | view: buildNewModalView(), 95 | }); 96 | if (modalResponse.error) { 97 | const error = `Failed to open a modal due to ${modalResponse.error}`; 98 | return { error }; 99 | } 100 | return { completed: false }; 101 | }) 102 | // Handle the button click events on the modal 103 | .addBlockActionsHandler("clear-inputs", async ({ body, client }) => { 104 | const response = await client.views.update({ 105 | interactivity_pointer: body.interactivity.interactivity_pointer, 106 | view_id: body.view.id, 107 | view: buildNewModalView(), 108 | }); 109 | if (response.error) { 110 | const error = `Failed to update a modal due to ${response.error}`; 111 | return { error }; 112 | } 113 | return { completed: false }; 114 | }) 115 | // Handle the data submission from the modal 116 | .addViewSubmissionHandler( 117 | ["deny-reason-submission"], 118 | ({ view }) => { 119 | const values = view.state.values; 120 | const reason = String(Object.values(values)[0]["deny-reason"].value); 121 | if (reason.length <= 5) { 122 | console.log(reason); 123 | const errors: Record = {}; 124 | const blockId = Object.keys(values)[0]; 125 | errors[blockId] = "The reason must be 5 characters or longer"; 126 | return { response_action: "errors", errors }; 127 | } 128 | return {}; 129 | }, 130 | ) 131 | // Handle the events when the end-user closes the modal 132 | .addViewClosedHandler( 133 | ["deny-reason-submission", "deny-reason-confirmation"], 134 | ({ view }) => { 135 | console.log(JSON.stringify(view, null, 2)); 136 | }, 137 | ); 138 | 139 | /** 140 | * Returns the initial state of the modal view 141 | * @returns the initial modal view 142 | */ 143 | function buildNewModalView() { 144 | return { 145 | "type": "modal", 146 | "callback_id": "deny-reason-submission", 147 | "title": { "type": "plain_text", "text": "Reason for the denial" }, 148 | "notify_on_close": true, 149 | "submit": { "type": "plain_text", "text": "Confirm" }, 150 | "blocks": [ 151 | { 152 | "type": "input", 153 | // If you reuse block_id when refreshing an existing modal view, 154 | // the old block may remain. To avoid this, always set a random value. 155 | "block_id": crypto.randomUUID(), 156 | "label": { "type": "plain_text", "text": "Reason" }, 157 | "element": { 158 | "type": "plain_text_input", 159 | "action_id": "deny-reason", 160 | "multiline": true, 161 | "placeholder": { 162 | "type": "plain_text", 163 | "text": "Share the reason why you denied the request in detail", 164 | }, 165 | }, 166 | }, 167 | { 168 | "type": "actions", 169 | "block_id": "clear", 170 | "elements": [ 171 | { 172 | type: "button", 173 | action_id: "clear-inputs", 174 | text: { type: "plain_text", text: "Clear all the inputs" }, 175 | style: "danger", 176 | }, 177 | ], 178 | }, 179 | ], 180 | }; 181 | } 182 | -------------------------------------------------------------------------------- /Button_Interactions/triggers/block_kit_button_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/block_kit_button_demo.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Block Kit Button Demo Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 15 | user_id: { value: TriggerContextData.Shortcut.user_id }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Button_Interactions/triggers/interactive_blocks_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/interactive_blocks_demo.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Interactive Blocks Demo Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 15 | user_id: { value: TriggerContextData.Shortcut.user_id }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Button_Interactions/workflows/block_kit_button_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as SendBlockKitMessage } from "../functions/send_block_kit_message.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "block-kit-button-demo-workflow", 6 | title: "Block Kit Button Demo Workflow", 7 | input_parameters: { 8 | properties: { 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | }, 12 | required: ["channel_id", "user_id"], 13 | }, 14 | }); 15 | 16 | workflow.addStep(SendBlockKitMessage, { 17 | user_id: workflow.inputs.user_id, 18 | channel_id: workflow.inputs.channel_id, 19 | }); 20 | 21 | export default workflow; 22 | -------------------------------------------------------------------------------- /Button_Interactions/workflows/interactive_blocks_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as HandleInteractiveBlocks } from "../functions/handle_interactive_blocks.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "interactive-blocks-demo-workflow", 6 | title: "Interactive Blocks Demo Workflow", 7 | input_parameters: { 8 | properties: { 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | }, 12 | required: ["channel_id", "user_id"], 13 | }, 14 | }); 15 | 16 | // Send a message via SendMessage + interactive_blocks 17 | const sendMessageStep = workflow.addStep(Schema.slack.functions.SendMessage, { 18 | channel_id: workflow.inputs.channel_id, 19 | message: `Do you approve <@${workflow.inputs.user_id}>'s time off request?`, 20 | // Simplified blocks for interactions 21 | interactive_blocks: [ 22 | { 23 | "type": "actions", 24 | "block_id": "approve-deny-buttons", 25 | "elements": [ 26 | { 27 | type: "button", 28 | action_id: "approve", 29 | text: { type: "plain_text", text: "Approve" }, 30 | style: "primary", 31 | }, 32 | { 33 | type: "button", 34 | action_id: "deny", 35 | text: { type: "plain_text", text: "Deny" }, 36 | style: "danger", 37 | }, 38 | ], 39 | }, 40 | ], 41 | }); 42 | 43 | // Handle the button click events on interactive_blocks 44 | workflow.addStep(HandleInteractiveBlocks, { 45 | // The clicked action's details 46 | action: sendMessageStep.outputs.action, 47 | // For further interactions on a modal 48 | interactivity: sendMessageStep.outputs.interactivity, 49 | // The message's URL 50 | messageLink: sendMessageStep.outputs.message_link, 51 | // The message's unique ID in the channel 52 | messageTs: sendMessageStep.outputs.message_context.message_ts, 53 | }); 54 | 55 | export default workflow; 56 | -------------------------------------------------------------------------------- /Canvases/README.md: -------------------------------------------------------------------------------- 1 | ## Supported Workflows 2 | 3 | - **Copy Canvas Demo Workflow:** Demonstrate how to create a canvas using 4 | `CanvasCreate` and copy the canvas using `CanvasCopy` 5 | - **Create Canvas Demo Workflow:** Demonstrate create a canvas using 6 | `CanvasCreate` 7 | - **Update Canvas Demo Workflow:** Demonstrate create a canvas using 8 | `CanvasCreate` and update that canvas content using `CanvasUpdateContent` 9 | - **Share Canvas Demo Workflow:** Demonstrate create a canvas using 10 | `CanvasCreate` and share the create canvas using `CanvasUpdateContent` to the 11 | user and channel that run the workflow 12 | 13 | ### Create a Link Trigger 14 | 15 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 16 | to run. These triggers can be invoked by a user or automatically as a response 17 | to an event within Slack. 18 | 19 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 20 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 21 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 22 | associated workflow. 23 | 24 | Link triggers are _unique to each installed version of your app_. This means 25 | that Shortcut URLs will be different across each workspace, as well as between 26 | [locally run](#running-your-project-locally) and 27 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 28 | the workspace that you'd like to create the trigger in. Each workspace has a 29 | development version (denoted by `(local)`), as well as a deployed version. 30 | 31 | To create a link trigger for the workflow in this template, run the following 32 | command: 33 | 34 | ```zsh 35 | $ slack trigger create --trigger-def ./Canvases/triggers/canvas_share_link.ts 36 | ``` 37 | 38 | After selecting a Workspace, the trigger should be created successfully. 39 | 40 | After selecting a Workspace, the output provided will include the link trigger 41 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 42 | a bookmark in a channel of the workspace you selected. 43 | 44 | ## Running Your Project Locally 45 | 46 | While building your app, you can see your changes propagated to your workspace 47 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 48 | is the development version if the name has the string `(local)` appended. 49 | 50 | ```zsh 51 | # Run app locally 52 | $ slack run 53 | 54 | Connected, awaiting events 55 | ``` 56 | 57 | Once running, click the 58 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 59 | `(local)` version of your app. This should start the included sample workflow. 60 | 61 | To stop running locally, press ` + C` to end the process. 62 | 63 | ## Manual Testing 64 | 65 | Once you click the link trigger in a channel, the trigger starts the 66 | `Canvases/workflows/*` workflow, which opens perform the canvas operation 67 | 68 | ## Create Canvas Demo Workflow 69 | 70 | To create a link trigger for the Block Kit based workflow in this template, run 71 | the following command: 72 | 73 | ```zsh 74 | $ slack trigger create --trigger-def ./Canvases/triggers/canvas_create_link.ts 75 | ``` 76 | 77 | You can run the workflow the same way as the above workflow. The workflow will 78 | create a canvas and set the owner of the canvas as the user that ran the 79 | workflow 80 | 81 | ## Copy Canvas Demo Workflow 82 | 83 | To create a link trigger for the Block Kit based workflow in this template, run 84 | the following command: 85 | 86 | ```zsh 87 | $ slack trigger create --trigger-def ./Canvases/triggers/canvas_copy_link.ts 88 | ``` 89 | 90 | You can run the workflow the same way as the above workflow. The workflow will 91 | create a canvas and then will create a copy of the created canvas 92 | 93 | ## Update Canvas Demo Workflow 94 | 95 | To create a link trigger for the Block Kit based workflow in this template, run 96 | the following command: 97 | 98 | ```zsh 99 | $ slack trigger create --trigger-def ./Canvases/triggers/canvas_update_link.ts 100 | ``` 101 | 102 | You can run the workflow the same way as the above workflow. The workflow will 103 | create a canvas and then will insert content to that canvas 104 | 105 | ## Share Canvas Demo Workflow 106 | 107 | To create a link trigger for the Block Kit based workflow in this template, run 108 | the following command: 109 | 110 | ```zsh 111 | $ slack trigger create --trigger-def ./Canvases/triggers/canvas_share_link.ts 112 | ``` 113 | 114 | You can run the workflow the same way as the above workflow. The workflow will 115 | create a canvas and then Share it with the user and channel that run the 116 | workflow 117 | 118 | ## Deploying Your App 119 | 120 | Once you're done with development, you can deploy the production version of your 121 | app to Slack hosting using `slack deploy`: 122 | 123 | ```zsh 124 | $ slack deploy 125 | ``` 126 | 127 | After deploying, create a trigger for the production version of your app (not 128 | appended with `(local)`). Once the trigger is invoked, the workflow should run 129 | just as it did when developing locally. 130 | 131 | ## Project Structure 132 | 133 | ### `manifest.ts` 134 | 135 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 136 | configuration. This file defines attributes like app name and description. 137 | 138 | ### `.slack/hooks.json` 139 | 140 | Used by the CLI to interact with the project's SDK dependencies. It contains 141 | script hooks that are executed by the CLI and implemented by the SDK. 142 | 143 | ### `Canvases/workflows` 144 | 145 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 146 | are executed in order. Each step in a workflow is a function. 147 | 148 | Workflows can be configured to run without user input, or they can collect 149 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 150 | continuing to the next step. 151 | 152 | ### `Canvases/triggers` 153 | 154 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 155 | are executed. A trigger file describes a scenario in which a workflow should be 156 | run, such as a user pressing a button or when a specific event occurs. 157 | 158 | ## What's Next? 159 | 160 | To learn more about other samples, visit [the top-level guide](../README.md) to 161 | find more! 162 | -------------------------------------------------------------------------------- /Canvases/triggers/canvas_copy_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/copy_canvas.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Create and copy Canvas Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel: { 15 | value: TriggerContextData.Shortcut.channel_id, 16 | }, 17 | user: { 18 | value: TriggerContextData.Shortcut.user_id, 19 | }, 20 | }, 21 | }; 22 | 23 | // Note that the Trigger object must be default-exported 24 | export default trigger; 25 | -------------------------------------------------------------------------------- /Canvases/triggers/canvas_create_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/create_canvas.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Create Canvas Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel: { 15 | value: TriggerContextData.Shortcut.channel_id, 16 | }, 17 | user: { 18 | value: TriggerContextData.Shortcut.user_id, 19 | }, 20 | }, 21 | }; 22 | 23 | // Note that the Trigger object must be default-exported 24 | export default trigger; 25 | -------------------------------------------------------------------------------- /Canvases/triggers/canvas_share_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/share_canvas.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Create Canvas and share Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel: { 15 | value: TriggerContextData.Shortcut.channel_id, 16 | }, 17 | user: { 18 | value: TriggerContextData.Shortcut.user_id, 19 | }, 20 | }, 21 | }; 22 | 23 | // Note that the Trigger object must be default-exported 24 | export default trigger; 25 | -------------------------------------------------------------------------------- /Canvases/triggers/canvas_update_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/update_canvas.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Create and update Canvas Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | channel: { 15 | value: TriggerContextData.Shortcut.channel_id, 16 | }, 17 | user: { 18 | value: TriggerContextData.Shortcut.user_id, 19 | }, 20 | }, 21 | }; 22 | 23 | // Note that the Trigger object must be default-exported 24 | export default trigger; 25 | -------------------------------------------------------------------------------- /Canvases/workflows/copy_canvas.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to create a canvas and copy it. 5 | * Note: Copy canvas only work for enterprise grid workspaces 6 | * 7 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 8 | * - "canvases:read" 9 | * - "canvases:write" 10 | * 11 | * Learn more about the copy canvas slack function: https://api.slack.com/reference/functions/canvas_copy 12 | * To learn more on workflows, read https://api.slack.com/automation/workflows 13 | */ 14 | const workflow = DefineWorkflow({ 15 | callback_id: "canvas-copy-workflow", 16 | title: "Create a canvas and copy it", 17 | description: "Create and copy a canvas", 18 | input_parameters: { 19 | properties: { 20 | channel: { 21 | type: Schema.slack.types.channel_id, 22 | }, 23 | user: { 24 | type: Schema.slack.types.user_id, 25 | }, 26 | }, 27 | required: ["channel"], 28 | }, 29 | }); 30 | 31 | const createCanvasStep = workflow.addStep( 32 | Schema.slack.functions.CanvasCreate, 33 | { 34 | title: "Hello world", 35 | owner_id: workflow.inputs.user, 36 | }, 37 | ); 38 | 39 | workflow.addStep( 40 | Schema.slack.functions.CanvasCopy, 41 | { 42 | canvas_id: createCanvasStep.outputs.canvas_id, 43 | title: "Copy of Hello world", 44 | owner_id: workflow.inputs.user, 45 | }, 46 | ); 47 | 48 | export default workflow; 49 | -------------------------------------------------------------------------------- /Canvases/workflows/create_canvas.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to create a canvas. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "canvases:write" 8 | * 9 | * Learn more about the create canvas slack function: https://api.slack.com/reference/functions/canvas_create 10 | * To learn more on workflows, read https://api.slack.com/automation/workflows 11 | */ 12 | const workflow = DefineWorkflow({ 13 | callback_id: "canvas-create-workflow", 14 | title: "Create a canvas", 15 | description: "Create a canvas", 16 | input_parameters: { 17 | properties: { 18 | channel: { 19 | type: Schema.slack.types.channel_id, 20 | }, 21 | user: { 22 | type: Schema.slack.types.user_id, 23 | }, 24 | }, 25 | required: ["channel"], 26 | }, 27 | }); 28 | 29 | workflow.addStep( 30 | Schema.slack.functions.CanvasCreate, 31 | { 32 | title: "Hello world", 33 | owner_id: workflow.inputs.user, 34 | content: [ 35 | { 36 | "type": "rich_text", 37 | "elements": [ 38 | { 39 | "type": "rich_text_section", 40 | "elements": [{ "type": "text", "text": "Hello new canvas" }], 41 | }, 42 | ], 43 | }, 44 | ], 45 | }, 46 | ); 47 | 48 | export default workflow; 49 | -------------------------------------------------------------------------------- /Canvases/workflows/share_canvas.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to create a canvas with content and share it with channels and user. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "canvases:write" 8 | * - "canvases:read" 9 | * - "chat:write" 10 | * - "chat:write.public" 11 | * - "im:write" 12 | * - "group:write" 13 | * 14 | * You also need the following configuration on `manifest.ts` file: 15 | * features: { 16 | * appHome: { 17 | * messagesTabEnabled: true, 18 | * messagesTabReadOnlyEnabled: false, 19 | * }, 20 | * }, 21 | * Learn more about the update canvas content slack function: https://api.slack.com/reference/functions/share_canvas 22 | * To learn more on workflows, read https://api.slack.com/automation/workflows 23 | */ 24 | const workflow = DefineWorkflow({ 25 | callback_id: "canvas-share-workflow", 26 | title: "Create and share a canvas", 27 | description: "Create and Share canvas", 28 | input_parameters: { 29 | properties: { 30 | channel: { 31 | type: Schema.slack.types.channel_id, 32 | }, 33 | user: { 34 | type: Schema.slack.types.user_id, 35 | }, 36 | }, 37 | required: ["channel"], 38 | }, 39 | }); 40 | 41 | const createCanvasStep = workflow.addStep( 42 | Schema.slack.functions.CanvasCreate, 43 | { 44 | title: "Hello world", 45 | owner_id: workflow.inputs.user, 46 | content: [ 47 | { 48 | "type": "rich_text", 49 | "elements": [ 50 | { 51 | "type": "rich_text_section", 52 | "elements": [{ "type": "text", "text": "Hello new canvas" }], 53 | }, 54 | ], 55 | }, 56 | ], 57 | }, 58 | ); 59 | 60 | workflow.addStep( 61 | Schema.slack.functions.ShareCanvas, 62 | { 63 | canvas_id: createCanvasStep.outputs.canvas_id, 64 | channel_ids: [workflow.inputs.channel], 65 | user_ids: [workflow.inputs.user], 66 | access_level: "edit", 67 | message: [ 68 | { 69 | "type": "rich_text", 70 | "elements": [ 71 | { 72 | "type": "rich_text_section", 73 | "elements": [{ 74 | "type": "text", 75 | "text": "Sharing the create canvas", 76 | }], 77 | }, 78 | ], 79 | }, 80 | ], 81 | }, 82 | ); 83 | 84 | export default workflow; 85 | -------------------------------------------------------------------------------- /Canvases/workflows/update_canvas.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how a canvas. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "canvases:write" 8 | * 9 | * Learn more about the update canvas content slack function: https://api.slack.com/reference/functions/canvas_update_content 10 | * To learn more on workflows, read https://api.slack.com/automation/workflows 11 | */ 12 | const workflow = DefineWorkflow({ 13 | callback_id: "canvas-update-workflow", 14 | title: "Create and update a canvas", 15 | description: "Create, Update and Share canvas", 16 | input_parameters: { 17 | properties: { 18 | channel: { 19 | type: Schema.slack.types.channel_id, 20 | }, 21 | user: { 22 | type: Schema.slack.types.user_id, 23 | }, 24 | }, 25 | required: ["channel"], 26 | }, 27 | }); 28 | 29 | const createCanvasStep = workflow.addStep( 30 | Schema.slack.functions.CanvasCreate, 31 | { 32 | title: "Hello world", 33 | owner_id: workflow.inputs.user, 34 | }, 35 | ); 36 | 37 | workflow.addStep( 38 | Schema.slack.functions.CanvasUpdateContent, 39 | { 40 | canvas_id: createCanvasStep.outputs.canvas_id, 41 | action: "append", 42 | content: [ 43 | { 44 | "type": "rich_text", 45 | "elements": [ 46 | { 47 | "type": "rich_text_section", 48 | "elements": [{ "type": "text", "text": "Updating the canvas" }], 49 | }, 50 | ], 51 | }, 52 | ], 53 | }, 54 | ); 55 | 56 | export default workflow; 57 | -------------------------------------------------------------------------------- /Connectors/README.md: -------------------------------------------------------------------------------- 1 | # Connector Functions 2 | 3 | This sub-app guides you on adding connector functions to your workflow. If you 4 | haven't set up the Slack CLI and the project on your local machine yet, visit 5 | [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **Giphy Workflow:** Post a channel message with a Giphy GIF image URL for a 10 | given text 11 | - **Google Calendar Workflow:** Create a new event on Google Calendar within 12 | Slack and then share the URL in a channel 13 | 14 | ## Giphy Workflow 15 | 16 | In this example workflow, you will create a link trigger, click the link to 17 | start the workflow, submit the modal data, and then see the outputs in the 18 | channel. 19 | 20 | ### Create a Link Trigger 21 | 22 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 23 | to run. These triggers can be invoked by a user or automatically as a response 24 | to an event within Slack. 25 | 26 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 27 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 28 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 29 | associated workflow. 30 | 31 | Link triggers are _unique to each installed version of your app_. This means 32 | that Shortcut URLs will be different across each workspace, as well as between 33 | [locally run](#running-your-project-locally) and 34 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 35 | the workspace that you'd like to create the trigger in. Each workspace has a 36 | development version (denoted by `(local)`), as well as a deployed version. 37 | 38 | To create a link trigger for the workflow in this template, run the following 39 | command: 40 | 41 | ```zsh 42 | $ slack trigger create --trigger-def ./Connectors/triggers/giphy.ts 43 | ``` 44 | 45 | After selecting a Workspace, the trigger should be created successfully. 46 | 47 | After selecting a Workspace, the output provided will include the link trigger 48 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 49 | a bookmark in a channel of the workspace you selected. 50 | 51 | ## Manual Testing 52 | 53 | Once you click the link trigger in a channel, the trigger starts the 54 | `Connectors/workflows/giphy.ts` workflow, which runs 55 | [a Giphy Connector function](https://api.slack.com/reference/connectors/giphy/get_translated_gif) 56 | along with other two Slack functions. 57 | 58 | When it's successful, you will see a modal dialog with lots of input fields. 59 | Once you submit it, you will receive a Giphy image URL in the same channel. 60 | 61 | ## Google Calendar Workflow 62 | 63 | In this example workflow, you will create a link trigger, click the link to 64 | start the workflow, submit the modal data, and then see the outputs in the 65 | channel. 66 | 67 | ### Create a Link Trigger 68 | 69 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 70 | to run. These triggers can be invoked by a user or automatically as a response 71 | to an event within Slack. 72 | 73 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 74 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 75 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 76 | associated workflow. 77 | 78 | Link triggers are _unique to each installed version of your app_. This means 79 | that Shortcut URLs will be different across each workspace, as well as between 80 | [locally run](#running-your-project-locally) and 81 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 82 | the workspace that you'd like to create the trigger in. Each workspace has a 83 | development version (denoted by `(local)`), as well as a deployed version. 84 | 85 | To create a link trigger for the workflow in this template, run the following 86 | command: 87 | 88 | ```zsh 89 | $ slack trigger create --trigger-def ./Connectors/triggers/google_calendar.ts 90 | ``` 91 | 92 | After selecting a Workspace, the trigger should be created successfully. 93 | 94 | After selecting a Workspace, the output provided will include the link trigger 95 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 96 | a bookmark in a channel of the workspace you selected. 97 | 98 | ## Running Your Project Locally 99 | 100 | While building your app, you can see your changes propagated to your workspace 101 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 102 | is the development version if the name has the string `(local)` appended. 103 | 104 | ```zsh 105 | # Run app locally 106 | $ slack run 107 | 108 | Connected, awaiting events 109 | ``` 110 | 111 | Once running, click the 112 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 113 | `(local)` version of your app. This should start the included sample workflow. 114 | 115 | To stop running locally, press ` + C` to end the process. 116 | 117 | ## Manual Testing 118 | 119 | Once you click the link trigger in a channel, the trigger starts the 120 | `Connectors/workflows/google_calendar.ts` workflow, which runs 121 | [a Google Calendar Connector function](https://api.slack.com/reference/connectors/google.calendar/create_event) 122 | along with other two Slack functions. 123 | 124 | When it's successful, you will see a modal dialog with lots of input fields. 125 | Once you submit it, the workflow creates a new event on the Google Calendar 126 | side, and then post the event URL in the same channel. 127 | 128 | ## Deploying Your App 129 | 130 | Once you're done with development, you can deploy the production version of your 131 | app to Slack hosting using `slack deploy`: 132 | 133 | ```zsh 134 | $ slack deploy 135 | ``` 136 | 137 | After deploying, create a trigger for the production version of your app (not 138 | appended with `(local)`). Once the trigger is invoked, the workflow should run 139 | just as it did when developing locally. 140 | 141 | ## Project Structure 142 | 143 | ### `manifest.ts` 144 | 145 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 146 | configuration. This file defines attributes like app name and description. 147 | 148 | ### `.slack/hooks.json` 149 | 150 | Used by the CLI to interact with the project's SDK dependencies. It contains 151 | script hooks that are executed by the CLI and implemented by the SDK. 152 | 153 | ### `Connectors/workflows` 154 | 155 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 156 | are executed in order. Each step in a workflow is a function. 157 | 158 | Workflows can be configured to run without user input, or they can collect 159 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 160 | continuing to the next step. 161 | 162 | ### `Connectors/triggers` 163 | 164 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 165 | are executed. A trigger file describes a scenario in which a workflow should be 166 | run, such as a user pressing a button or when a specific event occurs. 167 | 168 | ## What's Next? 169 | 170 | To learn more about other samples, visit [the top-level guide](../README.md) to 171 | find more! 172 | -------------------------------------------------------------------------------- /Connectors/triggers/giphy.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/giphy.ts"; 4 | 5 | const trigger: Trigger = { 6 | type: TriggerTypes.Shortcut, 7 | name: "Create a new Giphy URL", 8 | description: "Create a new Giphy URL within Slack", 9 | workflow: `#/workflows/${workflow.definition.callback_id}`, 10 | inputs: { 11 | interactivity: { value: TriggerContextData.Shortcut.interactivity }, 12 | user_id: { value: TriggerContextData.Shortcut.user_id }, 13 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 14 | }, 15 | }; 16 | 17 | export default trigger; 18 | -------------------------------------------------------------------------------- /Connectors/triggers/google_calendar.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/google_calendar.ts"; 4 | 5 | const trigger: Trigger = { 6 | type: TriggerTypes.Shortcut, 7 | name: "Create a new Google Calendar event", 8 | description: "Create a new Google Calendar event within Slack", 9 | workflow: `#/workflows/${workflow.definition.callback_id}`, 10 | inputs: { 11 | interactivity: { value: TriggerContextData.Shortcut.interactivity }, 12 | user_id: { value: TriggerContextData.Shortcut.user_id }, 13 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 14 | }, 15 | }; 16 | 17 | export default trigger; 18 | -------------------------------------------------------------------------------- /Connectors/workflows/giphy.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { Connectors } from "deno-slack-hub/mod.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "Giphy-Workflow", 6 | title: "Connector Example: Giphy", 7 | input_parameters: { 8 | properties: { 9 | interactivity: { type: Schema.slack.types.interactivity }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | channel_id: { type: Schema.slack.types.channel_id }, 12 | }, 13 | required: ["interactivity", "user_id", "channel_id"], 14 | }, 15 | }); 16 | 17 | const form = workflow.addStep(Schema.slack.functions.OpenForm, { 18 | title: "Generate Giphy URL", 19 | interactivity: workflow.inputs.interactivity, 20 | fields: { 21 | elements: [ 22 | { name: "text", title: "Search word", type: Schema.types.string }, 23 | ], 24 | required: ["text"], 25 | }, 26 | }); 27 | 28 | const gif = workflow.addStep( 29 | Connectors.Giphy.functions.GetTranslatedGif, 30 | { 31 | search_term: form.outputs.fields.text, 32 | weirdness: "0", 33 | }, 34 | ); 35 | 36 | workflow.addStep(Schema.slack.functions.SendMessage, { 37 | channel_id: workflow.inputs.channel_id, 38 | message: 39 | `<@${workflow.inputs.user_id}> posted "${form.outputs.fields.text}" GIF image:\n${gif.outputs.gif_title_url}`, 40 | }); 41 | 42 | export default workflow; 43 | -------------------------------------------------------------------------------- /Connectors/workflows/google_calendar.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { Connectors } from "deno-slack-hub/mod.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "Google-Calendar-Workflow", 6 | title: "Connector Example: Google Calendar", 7 | input_parameters: { 8 | properties: { 9 | interactivity: { type: Schema.slack.types.interactivity }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | channel_id: { type: Schema.slack.types.channel_id }, 12 | }, 13 | required: ["interactivity", "user_id", "channel_id"], 14 | }, 15 | }); 16 | 17 | const form = workflow.addStep(Schema.slack.functions.OpenForm, { 18 | title: "Create New Event", 19 | interactivity: workflow.inputs.interactivity, 20 | fields: { 21 | elements: [ 22 | { 23 | name: "summary", 24 | title: "Summary", 25 | type: Schema.types.string, 26 | }, 27 | { 28 | name: "start_time", 29 | title: "Start Time", 30 | type: Schema.slack.types.timestamp, 31 | }, 32 | { 33 | name: "end_time", 34 | title: "End Time", 35 | type: Schema.slack.types.timestamp, 36 | }, 37 | { 38 | name: "attendees", 39 | title: "Attendees", 40 | type: Schema.types.array, 41 | items: { type: Schema.slack.types.user_id }, 42 | default: [workflow.inputs.user_id], 43 | }, 44 | { 45 | name: "location", 46 | title: "Location", 47 | type: Schema.types.string, 48 | }, 49 | { 50 | name: "description", 51 | title: "Description", 52 | type: Schema.types.string, 53 | long: true, 54 | }, 55 | ], 56 | required: ["summary", "start_time", "end_time", "attendees"], 57 | }, 58 | }); 59 | 60 | const gcal = workflow.addStep(Connectors.GoogleCalendar.functions.CreateEvent, { 61 | google_access_token: { 62 | credential_source: "END_USER", 63 | }, 64 | summary: form.outputs.fields.summary, 65 | start_time: form.outputs.fields.start_time, 66 | end_time: form.outputs.fields.end_time, 67 | attendees: form.outputs.fields.attendees, 68 | location: form.outputs.fields.location, 69 | description: form.outputs.fields.description, 70 | }); 71 | 72 | workflow.addStep(Schema.slack.functions.SendMessage, { 73 | channel_id: workflow.inputs.channel_id, 74 | message: 75 | `:white_check_mark: <@${workflow.inputs.user_id}> created a new event "${form.outputs.fields.summary}" :point_right: ${gcal.outputs.event_link}`, 76 | }); 77 | 78 | export default workflow; 79 | -------------------------------------------------------------------------------- /Custom_Functions/README.md: -------------------------------------------------------------------------------- 1 | # Custom Functions 2 | 3 | This sub-app guides you on adding your custom function. If you haven't set up 4 | the Slack CLI and the project on your local machine yet, visit 5 | [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **MySendMessage Workflow:** Perform `chat.postMessage` API calls in a custom 10 | function 11 | 12 | ## MySendMessage Workflow 13 | 14 | In this example workflow, you will implement your own `SendMessage` function, 15 | which calls the `chat.postMessage` API. 16 | 17 | ### Create a Link Trigger 18 | 19 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 20 | to run. These triggers can be invoked by a user or automatically as a response 21 | to an event within Slack. 22 | 23 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 24 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 25 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 26 | associated workflow. 27 | 28 | Link triggers are _unique to each installed version of your app_. This means 29 | that Shortcut URLs will be different across each workspace, as well as between 30 | [locally run](#running-your-project-locally) and 31 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 32 | the workspace that you'd like to create the trigger in. Each workspace has a 33 | development version (denoted by `(local)`), as well as a deployed version. 34 | 35 | To create a link trigger for the workflow in this template, run the following 36 | command: 37 | 38 | ```zsh 39 | $ slack trigger create --trigger-def ./Custom_Functions/triggers/link.ts 40 | ``` 41 | 42 | After selecting a Workspace, the trigger should be created successfully. 43 | 44 | After selecting a Workspace, the output provided will include the link trigger 45 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 46 | a bookmark in a channel of the workspace you selected. 47 | 48 | ## Running Your Project Locally 49 | 50 | While building your app, you can see your changes propagated to your workspace 51 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 52 | is the development version if the name has the string `(local)` appended. 53 | 54 | ```zsh 55 | # Run app locally 56 | $ slack run 57 | 58 | Connected, awaiting events 59 | ``` 60 | 61 | Once running, click the 62 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 63 | `(local)` version of your app. This should start the included sample workflow. 64 | 65 | To stop running locally, press ` + C` to end the process. 66 | 67 | ## Manual Testing 68 | 69 | Once you click the link trigger in a channel, the trigger starts the 70 | `Custom_Functions/workflows/my_send_message.ts` workflow, which runs your custom 71 | function `Custom_Functions/functions/my_send_message.ts`. 72 | 73 | When it's successful, you will see a "Hello World!" message in the same channel. 74 | 75 | ## Testing 76 | 77 | For an example of how to test a function, see 78 | `Custom_Functions/functions/my_send_message.ts`. Test filenames should be 79 | suffixed with `_test`. You can run the test by running 80 | `deno test Custom_Functions/functions/my_send_message_test.ts` in your terminal 81 | window. 82 | 83 | ## Deploying Your App 84 | 85 | Once you're done with development, you can deploy the production version of your 86 | app to Slack hosting using `slack deploy`: 87 | 88 | ```zsh 89 | $ slack deploy 90 | ``` 91 | 92 | After deploying, create a trigger for the production version of your app (not 93 | appended with `(local)`). Once the trigger is invoked, the workflow should run 94 | just as it did when developing locally. 95 | 96 | ## Project Structure 97 | 98 | ### `manifest.ts` 99 | 100 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 101 | configuration. This file defines attributes like app name and description. 102 | 103 | ### `.slack/hooks.json` 104 | 105 | Used by the CLI to interact with the project's SDK dependencies. It contains 106 | script hooks that are executed by the CLI and implemented by the SDK. 107 | 108 | ### `Custom_Functions/functions` 109 | 110 | [Functions](https://api.slack.com/automation/functions) are reusable building 111 | blocks of automation that accept inputs, perform calculations, and provide 112 | outputs. Functions can be used independently or as steps in workflows. 113 | 114 | ### `Custom_Functions/workflows` 115 | 116 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 117 | are executed in order. Each step in a workflow is a function. 118 | 119 | Workflows can be configured to run without user input, or they can collect 120 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 121 | continuing to the next step. 122 | 123 | ### `Custom_Functions/triggers` 124 | 125 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 126 | are executed. A trigger file describes a scenario in which a workflow should be 127 | run, such as a user pressing a button or when a specific event occurs. 128 | 129 | ## What's Next? 130 | 131 | To learn more about other samples, visit [the top-level guide](../README.md) to 132 | find more! 133 | -------------------------------------------------------------------------------- /Custom_Functions/functions/my_send_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | export const def = DefineFunction({ 4 | callback_id: "my_send_message", 5 | title: "My SendMessage", 6 | source_file: "Custom_Functions/functions/my_send_message.ts", 7 | input_parameters: { 8 | properties: { 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | message: { type: Schema.types.string }, 11 | }, 12 | required: ["channel_id", "message"], 13 | }, 14 | output_parameters: { 15 | properties: { ts: { type: Schema.types.string } }, 16 | required: ["ts"], 17 | }, 18 | }); 19 | 20 | export default SlackFunction(def, async ({ inputs, client }) => { 21 | const response = await client.chat.postMessage({ 22 | channel: inputs.channel_id, 23 | text: inputs.message, 24 | }); 25 | console.log(`chat.postMessage result: ${JSON.stringify(response, null, 2)}`); 26 | if (response.error) { 27 | const error = `Failed to post a message due to ${response.error}`; 28 | return { error }; 29 | } 30 | return { outputs: { ts: response.ts } }; 31 | }); 32 | -------------------------------------------------------------------------------- /Custom_Functions/functions/my_send_message_test.ts: -------------------------------------------------------------------------------- 1 | import {stub} from "@std/testing/mock"; 2 | import { SlackFunctionTester } from "deno-slack-sdk/mod.ts"; 3 | import { assertEquals } from "@std/assert"; 4 | import handler from "./my_send_message.ts"; 5 | 6 | // Utility for generating valid arguments 7 | const { createContext } = SlackFunctionTester("my-function"); 8 | 9 | Deno.test("Send a message successfully", async () => { 10 | using _stubFetch = stub( 11 | globalThis, 12 | "fetch", 13 | (url: string | URL | Request, options?: RequestInit) => { 14 | const request = url instanceof Request ? url : new Request(url, options); 15 | 16 | assertEquals(request.method, "POST"); 17 | assertEquals(request.url, "https://slack.com/api/chat.postMessage"); 18 | 19 | const body = JSON.stringify({ ok: true, ts: "1111.2222" }); 20 | return Promise.resolve( 21 | new Response(body, { status: 200 }) 22 | ); 23 | } 24 | ); 25 | 26 | const inputs = { channel_id: "C111", message: "Hi there!" }; 27 | const token = "xoxb-valid"; 28 | const env = { LOG_LEVEL: "INFO" }; 29 | const { outputs, error } = await handler( 30 | createContext({ inputs, env, token }), 31 | ); 32 | assertEquals(error, undefined); 33 | assertEquals(outputs, { ts: "1111.2222" }); 34 | }); 35 | 36 | Deno.test("Fail to send a message with invalid token", async () => { 37 | using _stubFetch = stub( 38 | globalThis, 39 | "fetch", 40 | (url: string | URL | Request, options?: RequestInit) => { 41 | const request = url instanceof Request ? url : new Request(url, options); 42 | 43 | assertEquals(request.method, "POST"); 44 | assertEquals(request.url, "https://slack.com/api/chat.postMessage"); 45 | const authHeader = request.headers.get("Authorization"); 46 | if (authHeader !== "Bearer xoxb-valid") { 47 | // invalid token pattern 48 | const body = JSON.stringify({ ok: false, error: "invalid_auth" }); 49 | return Promise.resolve(new Response(body, { status: 200 })); 50 | } 51 | const body = JSON.stringify({ ok: true, ts: "1111.2222" }); 52 | return Promise.resolve(new Response(body, { status: 200 })); 53 | } 54 | ); 55 | 56 | const inputs = { channel_id: "C111", message: "Hi there!" }; 57 | const token = "xoxb-invalid"; 58 | const env = { LOG_LEVEL: "INFO" }; 59 | const { outputs, error } = await handler( 60 | createContext({ inputs, env, token }), 61 | ); 62 | assertEquals(error, "Failed to post a message due to invalid_auth"); 63 | assertEquals(outputs, undefined); 64 | }); 65 | 66 | Deno.test("Fail to send a message to an unknown channel", async () => { 67 | using _stubFetch = stub( 68 | globalThis, 69 | "fetch", 70 | async (url: string | URL | Request, options?: RequestInit) => { 71 | const request = url instanceof Request ? url : new Request(url, options); 72 | 73 | assertEquals(request.method, "POST"); 74 | assertEquals(request.url, "https://slack.com/api/chat.postMessage"); 75 | 76 | const params = await request.formData(); 77 | if (params.get("channel") !== "C111") { 78 | // unknown channel 79 | const body = JSON.stringify({ ok: false, error: "channel_not_found" }); 80 | return new Response(body, { status: 200 }); 81 | } 82 | const body = JSON.stringify({ ok: true, ts: "1111.2222" }); 83 | return new Response(body, { status: 200 }); 84 | } 85 | ); 86 | 87 | const inputs = { channel_id: "D111", message: "Hi there!" }; 88 | const token = "xoxb-valid"; 89 | const env = { LOG_LEVEL: "INFO" }; 90 | const { outputs, error } = await handler( 91 | createContext({ inputs, env, token }), 92 | ); 93 | assertEquals(error, "Failed to post a message due to channel_not_found"); 94 | assertEquals(outputs, undefined); 95 | }); 96 | -------------------------------------------------------------------------------- /Custom_Functions/triggers/link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/my_send_message_workflow.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "MySendMessage Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The channel where you click the link trigger 15 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Custom_Functions/workflows/my_send_message_workflow.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as MySendMessage } from "../functions/my_send_message.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "my-send-message-workflow", 6 | title: "MySendMessage Workflow", 7 | input_parameters: { 8 | properties: { 9 | channel_id: { 10 | type: Schema.slack.types.channel_id, 11 | description: "The channel ID passed from this workflow's trigger", 12 | }, 13 | }, 14 | required: ["channel_id"], 15 | }, 16 | }); 17 | 18 | workflow.addStep(MySendMessage, { 19 | channel_id: workflow.inputs.channel_id, 20 | message: "Hello World!", 21 | }); 22 | 23 | export default workflow; 24 | -------------------------------------------------------------------------------- /Datastores/README.md: -------------------------------------------------------------------------------- 1 | # Datastores 2 | 3 | This sub-app guides you on how to use 4 | [datastores](https://api.slack.com/automation/datastores). If you haven't set up 5 | the Slack CLI and the project on your local machine yet, visit 6 | [the top-level guide document](../README.md) first. 7 | 8 | ## Supported Workflows 9 | 10 | - **Task Manager Workflow:** Demonstrate how to interact with datastore records 11 | - **PTO Workflow:** Demonstrate how to use TTL for datastore records 12 | 13 | ## Task Manager Workflow 14 | 15 | In this example workflow, you will create a link trigger, click the link to 16 | start the workflow, and see the terminal output logs to know how the datastore 17 | API calls work. 18 | 19 | ### Create a Link Trigger 20 | 21 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 22 | to run. These triggers can be invoked by a user or automatically as a response 23 | to an event within Slack. 24 | 25 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 26 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 27 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 28 | associated workflow. 29 | 30 | Link triggers are _unique to each installed version of your app_. This means 31 | that Shortcut URLs will be different across each workspace, as well as between 32 | [locally run](#running-your-project-locally) and 33 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 34 | the workspace that you'd like to create the trigger in. Each workspace has a 35 | development version (denoted by `(local)`), as well as a deployed version. 36 | 37 | To create a link trigger for the workflow in this template, run the following 38 | command: 39 | 40 | ```zsh 41 | $ slack trigger create --trigger-def ./Datastores/triggers/link.ts 42 | ``` 43 | 44 | After selecting a Workspace, the trigger should be created successfully. 45 | 46 | After selecting a Workspace, the output provided will include the link trigger 47 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 48 | a bookmark in a channel of the workspace you selected. 49 | 50 | ## Running Your Project Locally 51 | 52 | While building your app, you can see your changes propagated to your workspace 53 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 54 | is the development version if the name has the string `(local)` appended. 55 | 56 | ```zsh 57 | # Run app locally 58 | $ slack run 59 | 60 | Connected, awaiting events 61 | ``` 62 | 63 | Once running, click the 64 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 65 | `(local)` version of your app. This should start the included sample workflow. 66 | 67 | To stop running locally, press ` + C` to end the process. 68 | 69 | ## Manual Testing 70 | 71 | Once you click the link trigger in a channel, the trigger starts the 72 | `Datastores/workflows/form_demo.ts` workflow, which runs the 73 | `Datastores/functions/tasks_demo.ts` function, which performs various datastore 74 | API calls and outputs it in the console. 75 | 76 | ## Run Queries via Slack CLI 77 | 78 | When you want to run ad-hoc queries and/or do bulk data inserts, 79 | `slack datastore query` command is useful for it! 80 | 81 | ```zsh 82 | $ slack datastore put '{"datastore": "tasks", "item": {"id": "1", "title": "Make a phone call"}}' 83 | 84 | 🎉 Stored below record in the datastore: tasks 85 | { 86 | "id": "1", 87 | "title": "Make a phone call" 88 | } 89 | 90 | $ slack datastore query ' 91 | { 92 | "datastore": "tasks", 93 | "expression": "begins_with(#title, :title)", 94 | "expression_attributes": {"#title": "title"}, 95 | "expression_values": {":title": "Make a "} 96 | } 97 | ' 98 | 99 | 🎉 Retrieved 1 items from datastore: tasks 100 | 101 | { 102 | "id": "1", 103 | "title": "Make a phone call" 104 | } 105 | ``` 106 | 107 | ## Deploying Your App 108 | 109 | Once you're done with development, you can deploy the production version of your 110 | app to Slack hosting using `slack deploy`: 111 | 112 | ```zsh 113 | $ slack deploy 114 | ``` 115 | 116 | After deploying, create a trigger for the production version of your app (not 117 | appended with `(local)`). Once the trigger is invoked, the workflow should run 118 | just as it did when developing locally. 119 | 120 | ## Project Structure 121 | 122 | ### `manifest.ts` 123 | 124 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 125 | configuration. This file defines attributes like app name and description. 126 | 127 | ### `.slack/hooks.json` 128 | 129 | Used by the CLI to interact with the project's SDK dependencies. It contains 130 | script hooks that are executed by the CLI and implemented by the SDK. 131 | 132 | ### `Datastores/datastores` 133 | 134 | [Datastores](https://api.slack.com/automation/datastores) can securely store and 135 | retrieve data for your application. Required scopes to use datastores include 136 | `datastore:write` and `datastore:read`. 137 | 138 | ### `Datastores/functions` 139 | 140 | [Functions](https://api.slack.com/automation/functions) are reusable building 141 | blocks of automation that accept inputs, perform calculations, and provide 142 | outputs. Functions can be used independently or as steps in workflows. 143 | 144 | ### `Datastores/workflows` 145 | 146 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 147 | are executed in order. Each step in a workflow is a function. 148 | 149 | Workflows can be configured to run without user input, or they can collect 150 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 151 | continuing to the next step. 152 | 153 | ### `Datastores/triggers` 154 | 155 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 156 | are executed. A trigger file describes a scenario in which a workflow should be 157 | run, such as a user pressing a button or when a specific event occurs. 158 | 159 | ## What's Next? 160 | 161 | To learn more about other samples, visit [the top-level guide](../README.md) to 162 | find more! 163 | -------------------------------------------------------------------------------- /Datastores/datastores/pto.ts: -------------------------------------------------------------------------------- 1 | import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | // Refer to https://api.slack.com/automation/datastores for details 4 | const datastore = DefineDatastore({ 5 | name: "pto_notifications", 6 | // The primary key's type must be string 7 | primary_key: "id", 8 | time_to_live_attribute: "end_at", 9 | attributes: { 10 | id: { type: Schema.types.string, required: true }, 11 | user_id: { type: Schema.slack.types.user_id, required: true }, 12 | start_at: { type: Schema.slack.types.timestamp, required: true }, 13 | end_at: { type: Schema.slack.types.timestamp, required: true }, 14 | note: { type: Schema.types.string, required: false }, 15 | }, 16 | }); 17 | 18 | export default datastore; 19 | -------------------------------------------------------------------------------- /Datastores/datastores/tasks.ts: -------------------------------------------------------------------------------- 1 | import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | // Refer to https://api.slack.com/automation/datastores for details 4 | const datastore = DefineDatastore({ 5 | name: "tasks", 6 | // The primary key's type must be string 7 | primary_key: "id", 8 | attributes: { 9 | id: { type: Schema.types.string, required: true }, 10 | title: { type: Schema.types.string, required: true }, 11 | description: { type: Schema.types.string }, // optional 12 | due: { type: Schema.types.string }, // optional 13 | }, 14 | }); 15 | 16 | export default datastore; 17 | -------------------------------------------------------------------------------- /Datastores/functions/pto_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | import PTO from "../datastores/pto.ts"; 3 | 4 | export const def = DefineFunction({ 5 | callback_id: "datastore-ttl-demo", 6 | title: "Datastore TTL demo", 7 | source_file: "Datastores/functions/pto_demo.ts", 8 | input_parameters: { 9 | properties: { 10 | interactivity: { type: Schema.slack.types.interactivity }, 11 | user_id: { type: Schema.slack.types.user_id }, 12 | }, 13 | required: ["interactivity", "user_id"], 14 | }, 15 | output_parameters: { properties: {}, required: [] }, 16 | }); 17 | 18 | export default SlackFunction(def, async ({ inputs, client }) => { 19 | const offset = (await client.users.info({ 20 | user: inputs.user_id, 21 | })).user?.tz_offset || 0; 22 | 23 | const { 24 | items: allItems, 25 | response_metadata, 26 | } = await client.apps.datastore.query({ datastore: PTO.name }); 27 | let cursor = response_metadata?.next_cursor; 28 | while (cursor) { 29 | const { 30 | items, 31 | response_metadata, 32 | } = await client.apps.datastore.query({ datastore: PTO.name, cursor }); 33 | allItems.push(...items); 34 | cursor = response_metadata?.next_cursor; 35 | } 36 | const blocksToDisplayPTOs: any[] = []; 37 | allItems.forEach((item) => { 38 | const startAt = new Date(item.start_at * 1000); 39 | startAt.setDate(startAt.getDate() - 1); 40 | const from = toDate(offset, startAt); 41 | const endAt = new Date(item.end_at * 1000); 42 | endAt.setDate(endAt.getDate() - 1); 43 | const to = toDate(offset, endAt); 44 | const note = item.note ? `(${item.note})` : ""; 45 | blocksToDisplayPTOs.push({ 46 | "type": "section", 47 | "text": { 48 | "type": "mrkdwn", 49 | "text": `<@${item.user_id}> ${from} - ${to} ${note}`, 50 | }, 51 | }); 52 | }); 53 | if (blocksToDisplayPTOs.length > 0) { 54 | blocksToDisplayPTOs.push({ "type": "divider" }); 55 | } 56 | 57 | const initialDate = toDate(offset); 58 | const blocks = [ 59 | { 60 | "type": "input", 61 | "block_id": "start_at", 62 | "element": { 63 | "type": "datepicker", 64 | "initial_date": initialDate, 65 | "action_id": "action", 66 | }, 67 | "label": { "type": "plain_text", "text": "Start at" }, 68 | "optional": false, 69 | }, 70 | { 71 | "type": "input", 72 | "block_id": "end_at", 73 | "element": { 74 | "type": "datepicker", 75 | "initial_date": initialDate, 76 | "action_id": "action", 77 | }, 78 | "label": { "type": "plain_text", "text": "End at" }, 79 | "optional": false, 80 | }, 81 | { 82 | "type": "input", 83 | "block_id": "note", 84 | "element": { "type": "plain_text_input", "action_id": "action" }, 85 | "label": { "type": "plain_text", "text": "Note" }, 86 | "optional": true, 87 | }, 88 | ]; 89 | 90 | const response = await client.views.open({ 91 | interactivity_pointer: inputs.interactivity.interactivity_pointer, 92 | view: { 93 | "type": "modal", 94 | "callback_id": "pto-submission", 95 | "title": { "type": "plain_text", "text": "PTOs" }, 96 | "submit": { "type": "plain_text", "text": "Submit" }, 97 | "close": { "type": "plain_text", "text": "Close" }, 98 | "blocks": blocksToDisplayPTOs.concat(blocks), 99 | }, 100 | }); 101 | if (response.error) { 102 | const error = 103 | `Failed to open a modal in the demo workflow. Contact the app maintainers with the following information - (error: ${response.error})`; 104 | return { error }; 105 | } 106 | return { 107 | // To continue with this interaction, return false for the completion 108 | completed: false, 109 | }; 110 | }) 111 | .addViewSubmissionHandler( 112 | ["pto-submission"], 113 | async ({ view, inputs, client }) => { 114 | const offset = 115 | (await client.users.info({ user: inputs.user_id })).user?.tz_offset || 116 | 0; 117 | const userId = inputs.user_id; 118 | const creation = await client.apps.datastore.put({ 119 | datastore: PTO.name, 120 | item: { 121 | id: userId + "-" + new Date().getTime(), 122 | user_id: userId, 123 | note: view.state.values.note.action.value, 124 | start_at: toTimestamp( 125 | offset, 126 | view.state.values.start_at.action.selected_date, 127 | ), 128 | end_at: toTimestamp( 129 | offset, 130 | view.state.values.end_at.action.selected_date, 131 | ), 132 | }, 133 | }); 134 | if (!creation.ok) { 135 | console.log( 136 | `Failed to create a data row: ${JSON.stringify(creation, null, 2)}`, 137 | ); 138 | return { error: creation.error }; 139 | } 140 | return { 141 | response_action: "update", 142 | view: { 143 | "type": "modal", 144 | "callback_id": "completion", 145 | "title": { "type": "plain_text", "text": "PTOs" }, 146 | "close": { "type": "plain_text", "text": "Close" }, 147 | "blocks": [ 148 | { 149 | "type": "section", 150 | "text": { "type": "mrkdwn", "text": "Thank you!" }, 151 | }, 152 | ], 153 | }, 154 | }; 155 | }, 156 | ); 157 | 158 | function toDate( 159 | offset: number, 160 | date: Date | undefined = undefined, 161 | ): string { 162 | const d = date ?? new Date(); 163 | d.setTime(d.getTime() + offset * 1000); 164 | const year = d.getFullYear(); 165 | const month = ("0" + String(d.getUTCMonth() + 1)).slice(-2); 166 | const day = ("0" + String(d.getUTCDate())).slice(-2); 167 | return `${year}-${month}-${day}`; 168 | } 169 | 170 | function toTimestamp( 171 | offset: number, 172 | date: string, 173 | ): number { 174 | const d = new Date(date); 175 | d.setTime(d.getTime() + offset * 1000); 176 | d.setDate(d.getDate() + 1); // set when the following day starts 177 | return d.getTime() / 1000; 178 | } 179 | -------------------------------------------------------------------------------- /Datastores/functions/tasks_demo.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | import Tasks from "../datastores/tasks.ts"; 3 | 4 | export const def = DefineFunction({ 5 | callback_id: "datastore-demo", 6 | title: "Datastore demo", 7 | source_file: "Datastores/functions/tasks_demo.ts", 8 | input_parameters: { properties: {}, required: [] }, 9 | output_parameters: { properties: {}, required: [] }, 10 | }); 11 | 12 | export default SlackFunction(def, async ({ client }) => { 13 | const creation = await client.apps.datastore.put({ 14 | datastore: Tasks.name, 15 | item: { "id": "1", "title": "Make a phone call to Jim" }, 16 | }); 17 | console.log(`creation result: ${JSON.stringify(creation, null, 2)}`); 18 | if (creation.error) { 19 | return { error: creation.error }; 20 | } 21 | 22 | const query = await client.apps.datastore.query({ 23 | datastore: Tasks.name, 24 | expression: "#id = :id", 25 | expression_attributes: { "#id": "id" }, 26 | expression_values: { ":id": "1" }, 27 | }); 28 | console.log(`query result: ${JSON.stringify(query, null, 2)}`); 29 | if (query.error) { 30 | return { error: query.error }; 31 | } 32 | 33 | const modification = await client.apps.datastore.put({ 34 | datastore: Tasks.name, 35 | item: { "id": "1", "title": "Make a phone call to Jim", "due": "Dec 18" }, 36 | }); 37 | console.log(`modification result: ${JSON.stringify(modification, null, 2)}`); 38 | if (modification.error) { 39 | return { error: modification.error }; 40 | } 41 | 42 | const deletion = await client.apps.datastore.delete({ 43 | datastore: Tasks.name, 44 | id: "1", 45 | }); 46 | console.log(`deletion result: ${JSON.stringify(deletion, null, 2)}`); 47 | if (deletion.error) { 48 | return { error: deletion.error }; 49 | } 50 | 51 | return { outputs: {} }; 52 | }); 53 | -------------------------------------------------------------------------------- /Datastores/triggers/pto_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/pto.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Datastore TTL Demo Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | interactivity: { value: "{{data.interactivity}}" }, 15 | user_id: { value: "{{data.user_id}}" }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Datastores/triggers/task_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/task_manager.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Datastore Demo Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: {}, 14 | }; 15 | 16 | // Note that the Trigger object must be default-exported 17 | export default trigger; 18 | -------------------------------------------------------------------------------- /Datastores/workflows/pto.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as PTODemo } from "../functions/pto_demo.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "datastore-ttl-demo-workflow", 6 | title: "Datastore TTL Demo Workflow", 7 | input_parameters: { 8 | properties: { 9 | interactivity: { type: Schema.slack.types.interactivity }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | }, 12 | required: ["interactivity", "user_id"], 13 | }, 14 | }); 15 | 16 | workflow.addStep(PTODemo, { 17 | interactivity: workflow.inputs.interactivity, 18 | user_id: workflow.inputs.user_id, 19 | }); 20 | 21 | export default workflow; 22 | -------------------------------------------------------------------------------- /Datastores/workflows/task_manager.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow } from "deno-slack-sdk/mod.ts"; 2 | import { def as TasksDemo } from "../functions/tasks_demo.ts"; 3 | 4 | const workflow = DefineWorkflow({ 5 | callback_id: "datastore-demo-workflow", 6 | title: "Datastore Demo Workflow", 7 | input_parameters: { properties: {}, required: [] }, 8 | }); 9 | 10 | workflow.addStep(TasksDemo, {}); 11 | 12 | export default workflow; 13 | -------------------------------------------------------------------------------- /Event_Triggers/README.md: -------------------------------------------------------------------------------- 1 | # Event Triggers 2 | 3 | This sub-app guides you on how to use 4 | [event triggers](https://api.slack.com/automation/triggers/event). If you 5 | haven't set up the Slack CLI and the project on your local machine yet, visit 6 | [the top-level guide document](../README.md) first. 7 | 8 | ## Supported Workflows 9 | 10 | - **Message To Channel Creator Workflow:** Post a message mentioning the channel 11 | creator when a channel is created 12 | - **Reply To Reaction Workflow:** Rely in a message's thread when a reaction is 13 | added to a message 14 | - **Ping Pong Message Workflow:** Post a "pong" message when receiving a message 15 | 16 | ## Message To Channel Creator Workflow 17 | 18 | This example workflow can be invoked when a public channel is created in the 19 | associated workspace. 20 | 21 | To create an event trigger for the workflow in this template, run the following 22 | command: 23 | 24 | ```zsh 25 | $ slack trigger create --trigger-def ./Event_Triggers/triggers/channel_created.ts 26 | ``` 27 | 28 | To verify the behavior, create a new public channel. If the app is running, this 29 | workflow will be invoked and its bot user will post a message mentioning you in 30 | the created channel :tada: 31 | 32 | ## Reply To Reaction Workflow 33 | 34 | This example workflow can be invoked when a reaction is added to a message in 35 | the associated channels. 36 | 37 | Before creating a trigger as usual, open the 38 | `Event_Triggers/triggers/reaction_added.ts` source file, and then **edit the 39 | part `channel_ids: ["C03E94MKS"],` with valid channel IDs in your workspace**. 40 | The easiest way to know a channel ID is to click a channel name in the Slack 41 | client UI, scroll down to the bottom in the popup modal, and then copy the 42 | string starting with a "C" letter. 43 | 44 | To create an event trigger for the workflow in this template, run the following 45 | command: 46 | 47 | ```zsh 48 | $ slack trigger create --trigger-def ./Event_Triggers/triggers/reaction_added.ts 49 | ``` 50 | 51 | To verify the behavior, add a reaction to a message in any of the specified 52 | channels. This workflow will be invoked and its bot user will post a message 53 | mentioning you in the message's thread. 54 | 55 | ## Running Your Project Locally 56 | 57 | While building your app, you can see your changes propagated to your workspace 58 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 59 | is the development version if the name has the string `(local)` appended. 60 | 61 | ```zsh 62 | # Run app locally 63 | $ slack run 64 | 65 | Connected, awaiting events 66 | ``` 67 | 68 | To stop running locally, press ` + C` to end the process. 69 | 70 | ## Deploying Your App 71 | 72 | Once you're done with development, you can deploy the production version of your 73 | app to Slack hosting using `slack deploy`: 74 | 75 | ```zsh 76 | $ slack deploy 77 | ``` 78 | 79 | After deploying, create a trigger for the production version of your app (not 80 | appended with `(local)`). Once the trigger is invoked, the workflow should run 81 | just as it did when developing locally. 82 | 83 | ## Project Structure 84 | 85 | ### `manifest.ts` 86 | 87 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 88 | configuration. This file defines attributes like app name and description. 89 | 90 | ### `.slack/hooks.json` 91 | 92 | Used by the CLI to interact with the project's SDK dependencies. It contains 93 | script hooks that are executed by the CLI and implemented by the SDK. 94 | 95 | ### `Event_Triggers/workflows` 96 | 97 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 98 | are executed in order. Each step in a workflow is a function. 99 | 100 | Workflows can be configured to run without user input, or they can collect 101 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 102 | continuing to the next step. 103 | 104 | ### `Event_Triggers/triggers` 105 | 106 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 107 | are executed. A trigger file describes a scenario in which a workflow should be 108 | run, such as a user pressing a button or when a specific event occurs. 109 | 110 | ## What's Next? 111 | 112 | To learn more about other samples, visit [the top-level guide](../README.md) to 113 | find more! 114 | -------------------------------------------------------------------------------- /Event_Triggers/triggers/channel_created.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { 3 | TriggerContextData, 4 | TriggerEventTypes, 5 | TriggerTypes, 6 | } from "deno-slack-api/mod.ts"; 7 | import workflow from "../workflows/message_to_channel_creator.ts"; 8 | 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Event, 11 | name: "Channel Creation Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | event: { event_type: TriggerEventTypes.ChannelCreated }, 14 | inputs: { 15 | channel_id: { value: TriggerContextData.Event.ChannelCreated.channel_id }, 16 | channel_name: { 17 | value: TriggerContextData.Event.ChannelCreated.channel_name, 18 | }, 19 | channel_type: { 20 | value: TriggerContextData.Event.ChannelCreated.channel_type, 21 | }, 22 | creator_id: { value: TriggerContextData.Event.ChannelCreated.creator_id }, 23 | created: { value: TriggerContextData.Event.ChannelCreated.created }, 24 | }, 25 | }; 26 | 27 | export default trigger; 28 | -------------------------------------------------------------------------------- /Event_Triggers/triggers/messages_posted.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { 3 | TriggerContextData, 4 | TriggerEventTypes, 5 | TriggerTypes, 6 | } from "deno-slack-api/mod.ts"; 7 | import workflow from "../workflows/ping_pong_message.ts"; 8 | 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Event, 11 | name: "Trigger the ping-pong message workflow", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | event: { 14 | event_type: TriggerEventTypes.MessagePosted, 15 | // TODO: Update this list 16 | channel_ids: ["C03E94MKS"], 17 | // See https://api.slack.com/automation/triggers/event for details 18 | filter: { 19 | version: 1, 20 | root: { 21 | operator: "OR", 22 | inputs: [ 23 | { statement: "{{data.text}} CONTAINS 'PING'" }, 24 | { statement: "{{data.text}} CONTAINS 'Ping'" }, 25 | { statement: "{{data.text}} CONTAINS 'ping'" }, 26 | ], 27 | }, 28 | }, 29 | }, 30 | inputs: { 31 | channel_id: { value: TriggerContextData.Event.MessagePosted.channel_id }, 32 | message_ts: { value: TriggerContextData.Event.MessagePosted.message_ts }, 33 | }, 34 | }; 35 | export default trigger; 36 | -------------------------------------------------------------------------------- /Event_Triggers/triggers/reaction_added.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { 3 | TriggerContextData, 4 | TriggerEventTypes, 5 | TriggerTypes, 6 | } from "deno-slack-api/mod.ts"; 7 | import workflow from "../workflows/reply_to_reaction.ts"; 8 | 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Event, 11 | name: "Trigger the example workflow", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | event: { 14 | event_type: TriggerEventTypes.ReactionAdded, 15 | // TODO: Update this list 16 | channel_ids: ["C03E94MKS"], 17 | }, 18 | inputs: { 19 | channel_id: { value: TriggerContextData.Event.ReactionAdded.channel_id }, 20 | user_id: { value: TriggerContextData.Event.ReactionAdded.user_id }, 21 | message_ts: { value: TriggerContextData.Event.ReactionAdded.message_ts }, 22 | reaction: { value: TriggerContextData.Event.ReactionAdded.reaction }, 23 | }, 24 | }; 25 | export default trigger; 26 | -------------------------------------------------------------------------------- /Event_Triggers/workflows/message_to_channel_creator.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | const workflow = DefineWorkflow({ 4 | callback_id: "message-to-channel-creator-workflow", 5 | title: "Message to Channel Creator Workflow", 6 | input_parameters: { 7 | properties: { 8 | // All the possible inputs from the "channel_created" event trigger 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | channel_name: { type: Schema.types.string }, 11 | channel_type: { type: Schema.types.string }, 12 | creator_id: { type: Schema.slack.types.user_id }, 13 | created: { type: Schema.types.string }, 14 | }, 15 | required: ["creator_id"], 16 | }, 17 | }); 18 | 19 | workflow.addStep(Schema.slack.functions.SendMessage, { 20 | channel_id: workflow.inputs.channel_id, 21 | message: 22 | `Hi <@${workflow.inputs.creator_id}>, thanks for creating this channel!`, 23 | }); 24 | 25 | export default workflow; 26 | -------------------------------------------------------------------------------- /Event_Triggers/workflows/ping_pong_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | const workflow = DefineWorkflow({ 4 | callback_id: "ping-pong-message-workflow", 5 | title: "Ping Pong Message Workflow", 6 | input_parameters: { 7 | properties: { 8 | // All the possible inputs from the "message_posted" event trigger 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | message_ts: { type: Schema.types.string }, 11 | }, 12 | required: ["channel_id", "message_ts"], 13 | }, 14 | }); 15 | 16 | workflow.addStep(Schema.slack.functions.ReplyInThread, { 17 | message_context: { 18 | channel_id: workflow.inputs.channel_id, 19 | message_ts: workflow.inputs.message_ts, 20 | }, 21 | message: ":wave: Pong!", 22 | }); 23 | 24 | export default workflow; 25 | -------------------------------------------------------------------------------- /Event_Triggers/workflows/reply_to_reaction.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | const workflow = DefineWorkflow({ 4 | callback_id: "reply-to-reaction-workflow", 5 | title: "Reply to Reaction Workflow", 6 | input_parameters: { 7 | properties: { 8 | // All the possible inputs from the "reaction_added" event trigger 9 | channel_id: { type: Schema.slack.types.channel_id }, 10 | user_id: { type: Schema.slack.types.user_id }, 11 | message_ts: { type: Schema.types.string }, 12 | reaction: { type: Schema.types.string }, 13 | }, 14 | required: ["channel_id", "user_id", "message_ts", "reaction"], 15 | }, 16 | }); 17 | 18 | workflow.addStep(Schema.slack.functions.ReplyInThread, { 19 | message_context: { 20 | channel_id: workflow.inputs.channel_id, 21 | message_ts: workflow.inputs.message_ts, 22 | }, 23 | message: 24 | `Hey <@${workflow.inputs.user_id}>, thanks for adding :${workflow.inputs.reaction}:!`, 25 | }); 26 | 27 | export default workflow; 28 | -------------------------------------------------------------------------------- /External_API_Calls/README.md: -------------------------------------------------------------------------------- 1 | # External API Calls 2 | 3 | This sub-app guides you on calling an external API in your custom function. If 4 | you haven't set up the Slack CLI and the project on your local machine yet, 5 | visit [the top-level guide document](../README.md) first. 6 | 7 | ## Supported Workflows 8 | 9 | - **External API Call Workflow:** Fetch data from https://httpbin.org/get , and 10 | then display it in an ephemeral message 11 | 12 | ## External API Call Workflow 13 | 14 | All the external domains must be listed in the `outgoingDomains` array in 15 | `manifest.ts` file. With that, your custom functions can perform HTTP requests 16 | to allowed domain URLs. 17 | 18 | Apart from that, nothing is different from the `Custom_Functions` sample app. 19 | 20 | ### Create a Link Trigger 21 | 22 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 23 | to run. These triggers can be invoked by a user or automatically as a response 24 | to an event within Slack. 25 | 26 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 27 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 28 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 29 | associated workflow. 30 | 31 | Link triggers are _unique to each installed version of your app_. This means 32 | that Shortcut URLs will be different across each workspace, as well as between 33 | [locally run](#running-your-project-locally) and 34 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 35 | the workspace that you'd like to create the trigger in. Each workspace has a 36 | development version (denoted by `(local)`), as well as a deployed version. 37 | 38 | To create a link trigger for the workflow in this template, run the following 39 | command: 40 | 41 | ```zsh 42 | $ slack trigger create --trigger-def ./External_API_Calls/triggers/link.ts 43 | ``` 44 | 45 | After selecting a Workspace, the trgger should be created successfully. 46 | 47 | After selecting a Workspace, the output provided will include the link trigger 48 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 49 | a bookmark in a channel of the workspace you selected. 50 | 51 | ## Running Your Project Locally 52 | 53 | While building your app, you can see your changes propagated to your workspace 54 | in real-time with `slack run`. In both the CLI and in Slack, you'll know an app 55 | is the development version if the name has the string `(local)` appended. 56 | 57 | ```zsh 58 | # Run app locally 59 | $ slack run 60 | 61 | Connected, awaiting events 62 | ``` 63 | 64 | Once running, click the 65 | [previously created Shortcut URL](#create-a-link-trigger) associated with the 66 | `(local)` version of your app. This should start the included sample workflow. 67 | 68 | To stop running locally, press ` + C` to end the process. 69 | 70 | ## Manual Testing 71 | 72 | Once you click the link trigger in a channel, the trigger starts the 73 | `External_API_Calls/workflows/ephemeral_message.ts` workflow, which runs your 74 | custom function `External_API_Calls/functions/httpbin_get.ts`. 75 | 76 | When it's successful, you will see the following ephemeral message in the same 77 | channel. The `user_agent` and `origin` can be different, though. 78 | 79 | ``` 80 | Received from httpbin.org: 81 | { 82 | "user_agent": "Deno/1.26.1", 83 | "origin": "35.74.58.174", 84 | "url": "https://httpbin.org/get" 85 | } 86 | ``` 87 | 88 | ## Testing 89 | 90 | For an example of how to test a function, see 91 | `External_API_Calls/functions/httpbin_get_test.ts`. Test filenames should be 92 | suffixed with `_test`. You can run the test by running 93 | `deno test External_API_Calls/functions/httpbin_get_test.ts` in your terminal 94 | window. 95 | 96 | ## Deploying Your App 97 | 98 | Once you're done with development, you can deploy the production version of your 99 | app to Slack hosting using `slack deploy`: 100 | 101 | ```zsh 102 | $ slack deploy 103 | ``` 104 | 105 | After deploying, create a trigger for the production version of your app (not 106 | appended with `(local)`). Once the trigger is invoked, the workflow should run 107 | just as it did when developing locally. 108 | 109 | ## Project Structure 110 | 111 | ### `manifest.ts` 112 | 113 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 114 | configuration. This file defines attributes like app name and description. 115 | 116 | ### `.slack/hooks.json` 117 | 118 | Used by the CLI to interact with the project's SDK dependencies. It contains 119 | script hooks that are executed by the CLI and implemented by the SDK. 120 | 121 | ### `External_API_Calls/functions` 122 | 123 | [Functions](https://api.slack.com/automation/functions) are reusable building 124 | blocks of automation that accept inputs, perform calculations, and provide 125 | outputs. Functions can be used independently or as steps in workflows. 126 | 127 | ### `External_API_Calls/workflows` 128 | 129 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 130 | are executed in order. Each step in a workflow is a function. 131 | 132 | Workflows can be configured to run without user input, or they can collect 133 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 134 | continuing to the next step. 135 | 136 | ### `External_API_Calls/triggers` 137 | 138 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 139 | are executed. A trigger file describes a scenario in which a workflow should be 140 | run, such as a user pressing a button or when a specific event occurs. 141 | 142 | ## What's Next? 143 | 144 | If you'd like to explore this concept more, the following sample apps also do 145 | external API calls. 146 | 147 | - https://github.com/slack-samples/deno-github-functions 148 | - https://github.com/slack-samples/deno-message-translator 149 | 150 | To learn more about other samples, visit [the top-level guide](../README.md) to 151 | find more! 152 | -------------------------------------------------------------------------------- /External_API_Calls/functions/httpbin_get.ts: -------------------------------------------------------------------------------- 1 | import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts"; 2 | 3 | export const def = DefineFunction({ 4 | callback_id: "httpbin_get", 5 | title: "Receive data from an httpbin.org API endpoint", 6 | source_file: "External_API_Calls/functions/httpbin_get.ts", 7 | input_parameters: { properties: {}, required: [] }, 8 | output_parameters: { 9 | properties: { 10 | user_agent: { type: Schema.types.string }, 11 | origin: { type: Schema.types.string }, 12 | url: { type: Schema.types.string }, 13 | }, 14 | required: [], 15 | }, 16 | }); 17 | 18 | export default SlackFunction(def, async () => { 19 | const response = await fetch("https://httpbin.org/get", {}); 20 | if (!response.ok) { 21 | const body = await response.text(); 22 | const error = 23 | `Failed to fetch data from httpbin.org (status: ${response.status}, body: ${body})`; 24 | return { error }; 25 | } 26 | const body = await response.json(); 27 | return { 28 | outputs: { 29 | url: body.url, 30 | origin: body.origin, 31 | user_agent: body.headers["User-Agent"], 32 | }, 33 | }; 34 | }); 35 | -------------------------------------------------------------------------------- /External_API_Calls/functions/httpbin_get_test.ts: -------------------------------------------------------------------------------- 1 | import {stub} from "@std/testing/mock"; 2 | import { assertEquals } from "@std/assert"; 3 | import { SlackFunctionTester } from "deno-slack-sdk/mod.ts"; 4 | import handler from "./httpbin_get.ts"; 5 | 6 | const { createContext } = SlackFunctionTester("my-function"); 7 | 8 | Deno.test("Perform an API call successfully", async () => { 9 | using _stubFetch = stub( 10 | globalThis, 11 | "fetch", 12 | (url: string | URL | Request, options?: RequestInit) => { 13 | const request = url instanceof Request ? url : new Request(url, options); 14 | 15 | assertEquals(request.method, "GET"); 16 | assertEquals(request.url, "https://httpbin.org/get"); 17 | 18 | return Promise.resolve( 19 | new Response(JSON.stringify({ 20 | "args": {}, 21 | "headers": { "User-Agent": "Deno/1.26.1" }, 22 | "origin": "35.74.58.174", 23 | "url": "https://httpbin.org/get", 24 | })) 25 | ); 26 | } 27 | ); 28 | 29 | const { outputs, error } = await handler(createContext({ inputs: {} })); 30 | assertEquals(outputs, { 31 | origin: "35.74.58.174", 32 | url: "https://httpbin.org/get", 33 | user_agent: "Deno/1.26.1", 34 | }); 35 | assertEquals(error, undefined); 36 | }); 37 | 38 | Deno.test("Fail to access the API endpoints", async () => { 39 | using _stubFetch = stub( 40 | globalThis, 41 | "fetch", 42 | (url: string | URL | Request, options?: RequestInit) => { 43 | const request = url instanceof Request ? url : new Request(url, options); 44 | 45 | assertEquals(request.method, "GET"); 46 | assertEquals(request.url, "https://httpbin.org/get"); 47 | 48 | return Promise.resolve( 49 | new Response("Bad Gateway", { status: 502 }) 50 | ); 51 | } 52 | ); 53 | 54 | const { outputs, error } = await handler(createContext({ inputs: {} })); 55 | assertEquals(outputs, undefined); 56 | assertEquals( 57 | error, 58 | "Failed to fetch data from httpbin.org (status: 502, body: Bad Gateway)", 59 | ); 60 | }); 61 | -------------------------------------------------------------------------------- /External_API_Calls/triggers/link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/ephemeral_message.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "httpbin.org Demo Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The channel where you click the link trigger 15 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 16 | // The user ID who clicks the link trigger 17 | user_id: { value: TriggerContextData.Shortcut.user_id }, 18 | }, 19 | }; 20 | 21 | // Note that the Trigger object must be default-exported 22 | export default trigger; 23 | -------------------------------------------------------------------------------- /External_API_Calls/workflows/ephemeral_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | import { def as HttpbinGet } from "../functions/httpbin_get.ts"; 3 | 4 | /** 5 | * This workflow demonstrates how to call an external API and share the result as an ephemeral message in a channel. 6 | * 7 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 8 | * - "chat:write" 9 | * - "chat:write.public" 10 | * 11 | * To learn more on workflows, read https://api.slack.com/automation/workflows 12 | */ 13 | const workflow = DefineWorkflow({ 14 | callback_id: "httpbin-demos-workflow", 15 | title: "httpbin.org Demo Workflow", 16 | input_parameters: { 17 | properties: { 18 | channel_id: { 19 | type: Schema.slack.types.channel_id, 20 | description: "The channel ID passed from this workflow's trigger", 21 | }, 22 | user_id: { 23 | type: Schema.slack.types.user_id, 24 | description: "The user ID passed from this workflow's trigger", 25 | }, 26 | }, 27 | required: ["channel_id", "user_id"], 28 | }, 29 | }); 30 | 31 | // Perform an external API call 32 | const httpbinGetStep = workflow.addStep(HttpbinGet, {}); 33 | 34 | // Send a message in a channel using the built-in function 35 | workflow.addStep(Schema.slack.functions.SendEphemeralMessage, { 36 | // Set the channel ID given by the trigger -> workflow 37 | channel_id: workflow.inputs.channel_id, 38 | // Set the user ID given by the trigger -> workflow 39 | user_id: workflow.inputs.user_id, 40 | message: "Received from httpbin.org: ```" + 41 | JSON.stringify(httpbinGetStep.outputs, null, 2) + 42 | "```", 43 | }); 44 | 45 | export default workflow; 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Slack Technologies, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Messaging/README.md: -------------------------------------------------------------------------------- 1 | # Messaging 2 | 3 | This sub-app guides you on how to post a channel message via the built-in 4 | [`SendMessage`](https://api.slack.com/reference/functions/send_message), 5 | [`SendEphemeralMessage`](https://api.slack.com/reference/functions/send_ephemeral_message), 6 | and [`SendDm`](https://api.slack.com/reference/functions/send_dm) functions. If 7 | you haven't set up the Slack CLI and the project on your local machine yet, 8 | visit [the top-level guide document](../README.md) first. 9 | 10 | ## Supported Workflows 11 | 12 | - **Channel Message Workflow:** Send an "in_channel" message, which is visible 13 | to everyone in a public channel 14 | - **Ephemeral Message Workflow:** Send an "ephemeral" message, which is visible 15 | only to a specific person in a public channel 16 | - **Direct Message Workflow:** Open a DM with a person and send a message in the 17 | private conversation 18 | 19 | ## Channel Message Workflow 20 | 21 | When you run `./Messaging/workflows/channel_message.ts` workflow, it sends a 22 | "Hello World!" message in a public channel. All you need to do are: 23 | 24 | 1. Create a trigger to start the workflow 25 | 2. Run your application using `slack run` 26 | 3. Invoke the workflow via the created trigger 27 | 28 | ### Create Triggers 29 | 30 | [Triggers](https://api.slack.com/automation/triggers) are what cause workflows 31 | to run. These triggers can be invoked by a user or automatically as a response 32 | to an event within Slack. 33 | 34 | By running this workflow, you will learn how to create and use two types of 35 | triggers. 36 | 37 | - Link trigger 38 | - Webhook trigger 39 | 40 | Either way, the workflow posts a message in a public channel. Let's start with a 41 | link trigger. 42 | 43 | #### Create a Link Trigger 44 | 45 | A [link trigger](https://api.slack.com/automation/triggers/link) is a type of 46 | trigger that generates a **Shortcut URL**, which, when posted in a channel or 47 | added as a bookmark, becomes a link. When clicked, the link trigger will run the 48 | associated workflow. 49 | 50 | Link triggers are _unique to each installed version of your app_. This means 51 | that Shortcut URLs will be different across each workspace, as well as between 52 | [locally run](#running-your-project-locally) and 53 | [deployed apps](#deploying-your-app). When creating a trigger, you must select 54 | the workspace that you'd like to create the trigger in. Each workspace has a 55 | development version (denoted by `(local)`), as well as a deployed version. 56 | 57 | To create a link trigger for the workflow in this template, run the following 58 | command: 59 | 60 | ```zsh 61 | $ slack trigger create --trigger-def ./Messaging/triggers/channel_message_link.ts 62 | ``` 63 | 64 | After selecting a Workspace, the output provided will include the link trigger 65 | Shortcut URL. Copy and paste this URL into a channel as a message, or add it as 66 | a bookmark in a channel of the workspace you selected. 67 | 68 | Running `slack trigger create` command automatically installs your app with the 69 | latest manifest metadata. Thus, your workflow is now ready to invoke. Once you 70 | run the workflow, you will see "Hello World!" message in the same channel :tada: 71 | 72 | #### Create a Webhook Trigger 73 | 74 | A [webhook trigger](https://api.slack.com/automation/triggers/webhook) is a type 75 | of a trigger that runs its associated workflow when a specific URL receives a 76 | POST request. 77 | 78 | To create a webhook trigger for the workflow in this template, run the following 79 | command: 80 | 81 | ```zsh 82 | $ slack trigger create --trigger-def ./Messaging/triggers/channel_message_webhook.ts 83 | ``` 84 | 85 | After selecting a Workspace, the output provided will include the webhook URL 86 | starting with `https://hooks.slack.com/triggers/`. You can send "Hello World!" 87 | message to a public channel by sending an HTTP POST request with a channel ID to 88 | the webhook URL. The easiest way to know a channel ID is to click a channel name 89 | in the Slack client UI, scroll down to the bottom in the popup modal, and then 90 | copy the string starting with a "C" letter. 91 | 92 | ```zsh 93 | $ curl -XPOST https://hooks.slack.com/triggers/your-url..... -d '{"channel_id": "C1234567890"}' 94 | ``` 95 | 96 | When you pass a valid **public** channel in the request body, you will see the 97 | same "Hello World" message in the specified channel :tada: 98 | 99 | ## Ephemeral Message Workflow 100 | 101 | Similarly, you can send an ephemeral message, which is visible to a specific 102 | user, from a workflow. Create a link trigger: 103 | 104 | ```zsh 105 | $ slack trigger create --trigger-def ./Messaging/triggers/ephemeral_message_link.ts 106 | ``` 107 | 108 | You can invoke the `./Messaging/workflows/ephemeral_message.ts` workflow from 109 | the link trigger button. Then, you will receive an _"Only visible to you"_ 110 | message from the app. 111 | 112 | ## Direct Message Workflow 113 | 114 | Lastly, try one more built-in function, which sends a direct message from your 115 | app. 116 | 117 | ```zsh 118 | $ slack trigger create --trigger-def ./Messaging/triggers/direct_message_link.ts 119 | ``` 120 | 121 | When you invoke the `./Messaging/workflows/direct_message.ts` workflow from the 122 | link trigger, you will receive a DM from the app :tada: 123 | 124 | ## Deploying Your App 125 | 126 | Once you're done with development, you can deploy the production version of your 127 | app to Slack hosting using `slack deploy`: 128 | 129 | ```zsh 130 | $ slack deploy 131 | ``` 132 | 133 | After deploying, create a trigger for the production version of your app (not 134 | appended with `(local)`). Once the trigger is invoked, the workflow should run 135 | just as it did when developing locally. 136 | 137 | ### Viewing Activity Logs 138 | 139 | Activity logs for the production instance of your application can be viewed with 140 | the `slack activity` command: 141 | 142 | ```zsh 143 | $ slack activity 144 | ``` 145 | 146 | ## Project Structure 147 | 148 | ### `manifest.ts` 149 | 150 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 151 | configuration. This file defines attributes like app name and description. 152 | 153 | ### `.slack/hooks.json` 154 | 155 | Used by the CLI to interact with the project's SDK dependencies. It contains 156 | script hooks that are executed by the CLI and implemented by the SDK. 157 | 158 | ### `Messaging/workflows` 159 | 160 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 161 | are executed in order. Each step in a workflow is a function. 162 | 163 | Workflows can be configured to run without user input, or they can collect 164 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 165 | continuing to the next step. 166 | 167 | ### `Messaging/triggers` 168 | 169 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 170 | are executed. A trigger file describes a scenario in which a workflow should be 171 | run, such as a user pressing a button or when a specific event occurs. 172 | 173 | ## What's Next? 174 | 175 | To learn more about other samples, visit [the top-level guide](../README.md) to 176 | find more! 177 | -------------------------------------------------------------------------------- /Messaging/triggers/channel_message_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/channel_message.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Channel Messaging Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The channel where you click the link trigger 15 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Messaging/triggers/channel_message_webhook.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/channel_message.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/webhook 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Webhook, // Incoming Webhooks 11 | name: "Channel Messaging Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The channel must be included in request body data 15 | channel_id: { value: "{{data.channel_id}}" }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Messaging/triggers/direct_message_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/direct_message.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Direct Messaging Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The user ID who clicks the link trigger 15 | user_id: { value: TriggerContextData.Shortcut.user_id }, 16 | }, 17 | }; 18 | 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Messaging/triggers/ephemeral_message_link.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/ephemeral_message.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow when an end-user clicks the link. 7 | * Learn more at https://api.slack.com/automation/triggers/link 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Shortcut, 11 | name: "Ephemeral Messaging Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | inputs: { 14 | // The channel where you click the link trigger 15 | channel_id: { value: TriggerContextData.Shortcut.channel_id }, 16 | // The user ID who clicks the link trigger 17 | user_id: { value: TriggerContextData.Shortcut.user_id }, 18 | }, 19 | }; 20 | 21 | // Note that the Trigger object must be default-exported 22 | export default trigger; 23 | -------------------------------------------------------------------------------- /Messaging/workflows/channel_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to post a message in a channel. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "chat:write" 8 | * - "chat:write.public" 9 | * 10 | * To learn more on workflows, read https://api.slack.com/automation/workflows 11 | */ 12 | const workflow = DefineWorkflow({ 13 | callback_id: "channel-message-workflow", 14 | title: "Channel Message Workflow", 15 | input_parameters: { 16 | properties: { 17 | channel_id: { 18 | type: Schema.slack.types.channel_id, 19 | description: "The channel ID passed from this workflow's trigger", 20 | }, 21 | }, 22 | required: ["channel_id"], 23 | }, 24 | }); 25 | 26 | // Send a message in a channel using the built-in function 27 | workflow.addStep(Schema.slack.functions.SendMessage, { 28 | // Set the channel ID given by the trigger -> workflow 29 | channel_id: workflow.inputs.channel_id, 30 | message: "Hello World!", 31 | }); 32 | 33 | export default workflow; 34 | -------------------------------------------------------------------------------- /Messaging/workflows/direct_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to send a DM. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "chat:write" 8 | * 9 | * To learn more on workflows, read https://api.slack.com/automation/workflows 10 | */ 11 | const workflow = DefineWorkflow({ 12 | callback_id: "direct-message-workflow", 13 | title: "Direct Message Workflow", 14 | input_parameters: { 15 | properties: { 16 | user_id: { 17 | type: Schema.slack.types.user_id, 18 | description: "The user ID passed from this workflow's trigger", 19 | }, 20 | }, 21 | required: ["user_id"], 22 | }, 23 | }); 24 | 25 | // Send a message in a channel using the built-in function 26 | workflow.addStep(Schema.slack.functions.SendDm, { 27 | // Set the user ID given by the trigger -> workflow 28 | user_id: workflow.inputs.user_id, 29 | message: "Hello World!", 30 | }); 31 | 32 | export default workflow; 33 | -------------------------------------------------------------------------------- /Messaging/workflows/ephemeral_message.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow demonstrates how to post an ephemeral message in a channel. 5 | * 6 | * To run the workflow, you need to have the following scopes in "botScopes" property in `manifest.ts` file: 7 | * - "chat:write" 8 | * - "chat:write.public" 9 | * 10 | * To learn more on workflows, read https://api.slack.com/automation/workflows 11 | */ 12 | const workflow = DefineWorkflow({ 13 | callback_id: "ephemeral-message-workflow", 14 | title: "Ephemeral Message Workflow", 15 | input_parameters: { 16 | properties: { 17 | channel_id: { 18 | type: Schema.slack.types.channel_id, 19 | description: "The channel ID passed from this workflow's trigger", 20 | }, 21 | user_id: { 22 | type: Schema.slack.types.user_id, 23 | description: "The user ID passed from this workflow's trigger", 24 | }, 25 | }, 26 | required: ["channel_id", "user_id"], 27 | }, 28 | }); 29 | 30 | // Send a message in a channel using the built-in function 31 | workflow.addStep(Schema.slack.functions.SendEphemeralMessage, { 32 | // Set the channel ID given by the trigger -> workflow 33 | channel_id: workflow.inputs.channel_id, 34 | // Set the user ID given by the trigger -> workflow 35 | user_id: workflow.inputs.user_id, 36 | message: "Hello World!", 37 | }); 38 | 39 | export default workflow; 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deno Code Snippets 2 | 3 | This app demonstrates most of Slack's next-generation platform features in a 4 | simple form. Refer to each sub app's README for more details. 5 | 6 | **Guide Outline**: 7 | 8 | - [Setup](#setup) 9 | - [Install the Slack CLI](#install-the-slack-cli) 10 | - [Clone the Sample App](#clone-the-sample-app) 11 | - [Sample Apps](#sample-apps) 12 | - [Resources](#resources) 13 | 14 | --- 15 | 16 | ## Setup 17 | 18 | Before getting started, make sure you have a development workspace where you 19 | have permissions to install apps. If you don’t have one set up, go ahead and 20 | [create one](https://slack.com/create). Also, please note that the workspace 21 | requires any of [the Slack paid plans](https://slack.com/pricing). 22 | 23 | ### Install the Slack CLI 24 | 25 | To use this template, you first need to install and configure the Slack CLI. 26 | Step-by-step instructions can be found in our 27 | [Quickstart Guide](https://api.slack.com/automation/quickstart). 28 | 29 | ### Clone the Sample App 30 | 31 | Start by cloning this repository: 32 | 33 | ```zsh 34 | # Clone this project onto your machine 35 | $ slack create my-code-snippets-app -t slack-samples/deno-code-snippets 36 | 37 | # Change into this project directory 38 | $ cd my-code-snippets-app 39 | ``` 40 | 41 | ## Sample Apps 42 | 43 | This app consists of a number of sample apps. Go over the list of sub-apps below 44 | and start with any of them: 45 | 46 | - [Messaging](./Messaging/): Post a channel message via the built-in 47 | [`SendMessage`](https://api.slack.com/reference/functions/send_message), 48 | [`SendEphemeralMessage`](https://api.slack.com/reference/functions/send_ephemeral_message), 49 | and [`SendDm`](https://api.slack.com/reference/functions/send_dm) functions 50 | - [Built-in Forms](./Built-in_Forms/): Open a modal dialog using the built-in 51 | [`OpenForm`](https://api.slack.com/reference/functions/open_form) 52 | - [Canvases](./Canvases/): Use canvas via the built-in 53 | [`CopyCanvas`](https://api.slack.com/reference/functions/canvas_copy), 54 | [`CreateCanvas`](https://api.slack.com/reference/functions/canvas_create), 55 | [`CanvasUpdateContent`](https://api.slack.com/reference/functions/canvas_update_content), 56 | [`ShareCanvas`](https://api.slack.com/reference/functions/share_canvas), 57 | - [Connectors](./Connectors/): Use connector steps within your coded workflow 58 | - [Custom Functions](./Custom_Functions/) function: Do anything you want by 59 | writing TypeScript code 60 | - [External API Calls](./External_API_Calls/): Call other service's APIs in your 61 | custom function 62 | - [Datastores](./Datastores/): Use datastores to store your app's data 63 | - [Event Triggers](./Event_Triggers/): Start a workflow when a Slack event 64 | occurs 65 | - [Scheduled Triggers](./Scheduled_Triggers/): Schedule workflow executions 66 | 67 | The following ones may be a little bit advanced: 68 | 69 | - [Button Interactions](./Button_Interactions/): Place a button in a message and 70 | handle the interactions with a custom function 71 | - [Block Kit Modals](./Block_Kit_Modals/): Fully leverage Slack's 72 | [modals](https://api.slack.com/surfaces/modals/using) and its foundation, 73 | [Block Kit UI framework](https://api.slack.com/block-kit) 74 | 75 | ## Resources 76 | 77 | To learn more about developing with the CLI, you can visit the following guides: 78 | 79 | - [Creating a new app with the CLI](https://api.slack.com/automation/create) 80 | - [Configuring your app](https://api.slack.com/automation/manifest) 81 | - [Developing locally](https://api.slack.com/automation/run) 82 | 83 | To view all documentation and guides available, visit the 84 | [Overview page](https://api.slack.com/automation/overview). 85 | -------------------------------------------------------------------------------- /Scheduled_Triggers/README.md: -------------------------------------------------------------------------------- 1 | # Scheduled Triggers 2 | 3 | This sub-app guides you on invoking a workflow using a 4 | [scheduled trigger](https://api.slack.com/automation/triggers/scheduled). A 5 | scheduled trigger can invoke a workflow periodically or only once. The supported 6 | intervals as of this writing are "once", "hourly", "daily", "weekly", "monthly", 7 | and "yearly". 8 | 9 | If you haven't set up the Slack CLI and the project on your local machine yet, 10 | visit [the top-level guide document](../README.md) first. 11 | 12 | ## Supported Workflows 13 | 14 | - **Scheduled Workflow:** Do nothing apart from Slack server-side logging 15 | 16 | ## Scheduled Workflow 17 | 18 | To create a scheduled trigger for the workflow in this template, run the 19 | following command: 20 | 21 | ```zsh 22 | $ slack trigger create --trigger-def ./Scheduled_Triggers/triggers/scheduled_only_once.ts 23 | ``` 24 | 25 | After selecting a Workspace, the trigger should be created successfully. 26 | 27 | The sample workflow will be invoked in 5 seconds, but it actually does nothing. 28 | To confirm that the workflow is invoked for sure, run the following command to 29 | check Slack's server-side logs in your termninal. 30 | 31 | ```zsh 32 | $ slack activity -t 33 | ``` 34 | 35 | You will see a few lines of logs like this: 36 | 37 | ``` 38 | 2023-01-20 10:17:06 [info] [Fn04KS1D0335] (Trace=Tr04KUKQDD0U) Function execution started for workflow function 'Scheduled Workflow' 39 | 2023-01-20 10:17:06 [info] [Wf04KUJK493N] (Trace=Tr04KP7MHUPP) Execution started for workflow 'Scheduled Workflow' 40 | 2023-01-20 10:17:07 [info] [Fn04KS1D0335] (Trace=Tr04KUKQDD0U) Function execution completed for function 'Scheduled Workflow' 41 | 2023-01-20 10:17:07 [info] [Wf04KUJK493N] (Trace=Tr04KP7MHUPP) Execution completed for workflow 'Scheduled Workflow' 42 | ``` 43 | 44 | ## Deploying Your App 45 | 46 | Once you're done with development, you can deploy the production version of your 47 | app to Slack hosting using `slack deploy`: 48 | 49 | ```zsh 50 | $ slack deploy 51 | ``` 52 | 53 | After deploying, create a trigger for the production version of your app (not 54 | appended with `(local)`). Once the trigger is invoked, the workflow should run 55 | just as it did when developing locally. 56 | 57 | ## Project Structure 58 | 59 | ### `manifest.ts` 60 | 61 | The [app manifest](https://api.slack.com/automation/manifest) contains the app's 62 | configuration. This file defines attributes like app name and description. 63 | 64 | ### `.slack/hooks.json` 65 | 66 | Used by the CLI to interact with the project's SDK dependencies. It contains 67 | script hooks that are executed by the CLI and implemented by the SDK. 68 | 69 | ### `Scheduled_Triggers/workflows` 70 | 71 | A [workflow](https://api.slack.com/automation/workflows) is a set of steps that 72 | are executed in order. Each step in a workflow is a function. 73 | 74 | Workflows can be configured to run without user input, or they can collect 75 | inputs by beginning with a [form](https://api.slack.com/automation/forms) before 76 | continuing to the next step. 77 | 78 | ### `Scheduled_Triggers/triggers` 79 | 80 | [Triggers](https://api.slack.com/automation/triggers) determine when workflows 81 | are executed. A trigger file describes a scenario in which a workflow should be 82 | run, such as a user pressing a button or when a specific event occurs. 83 | 84 | ## What's Next? 85 | 86 | The `do_nothing` workflow does nothing as its name implies. To learn how to 87 | build more meaningful workflows, please check out the following sample 88 | workflows. The workflows can be invoked every day to maintain some data. 89 | 90 | - https://github.com/slack-samples/deno-message-translator/blob/main/triggers/daily_maintenance_job.ts 91 | - https://github.com/slack-samples/deno-message-translator/blob/main/workflows/maintenance_job.ts 92 | 93 | To learn more about other samples, visit [the top-level guide](../README.md) to 94 | find more! 95 | -------------------------------------------------------------------------------- /Scheduled_Triggers/triggers/scheduled_only_once.ts: -------------------------------------------------------------------------------- 1 | import { Trigger } from "deno-slack-sdk/types.ts"; 2 | import { TriggerTypes } from "deno-slack-api/mod.ts"; 3 | import workflow from "../workflows/do_nothing.ts"; 4 | 5 | /** 6 | * This trigger starts the workflow at specific time intervals. 7 | * Learn more at https://api.slack.com/automation/triggers/scheduled 8 | */ 9 | const trigger: Trigger = { 10 | type: TriggerTypes.Scheduled, 11 | name: "Schedule Workflow Trigger", 12 | workflow: `#/workflows/${workflow.definition.callback_id}`, 13 | schedule: { 14 | // This start_time means 5 seconds after you run `slack trigger create` command 15 | start_time: new Date(new Date().getTime() + 5_000).toISOString(), 16 | frequency: { type: "once" }, 17 | }, 18 | }; 19 | // Note that the Trigger object must be default-exported 20 | export default trigger; 21 | -------------------------------------------------------------------------------- /Scheduled_Triggers/workflows/do_nothing.ts: -------------------------------------------------------------------------------- 1 | import { DefineWorkflow } from "deno-slack-sdk/mod.ts"; 2 | 3 | /** 4 | * This workflow does nothing, but you can confirm it was invoked by checking `slack activity -t` command outputs. 5 | * To learn more on workflows, read https://api.slack.com/automation/workflows 6 | */ 7 | const workflow = DefineWorkflow({ 8 | callback_id: "scheduled-workflow", 9 | title: "Scheduled Workflow", 10 | input_parameters: { properties: {}, required: [] }, 11 | }); 12 | 13 | export default workflow; 14 | -------------------------------------------------------------------------------- /assets/default_new_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slack-samples/deno-code-snippets/93d97228653478dbd7490a8af4a47108aef1de91/assets/default_new_app_icon.png -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", 3 | "fmt": { 4 | "include": [ 5 | "README.md", 6 | "**/README.md", 7 | "**/datastores", 8 | "**/external_auth", 9 | "**/functions", 10 | "manifest.ts", 11 | "**/triggers", 12 | "**/types", 13 | "**/views", 14 | "**/workflows" 15 | ] 16 | }, 17 | "lint": { 18 | "include": [ 19 | "**/datastores", 20 | "**/external_auth", 21 | "**/functions", 22 | "manifest.ts", 23 | "**/triggers", 24 | "**/types", 25 | "**/views", 26 | "**/workflows" 27 | ] 28 | }, 29 | "lock": false, 30 | "tasks": { 31 | "test": "deno fmt --check && deno lint && deno test --allow-read" 32 | }, 33 | "imports": { 34 | "deno-slack-sdk/": "https://deno.land/x/deno_slack_sdk@2.15.0/", 35 | "deno-slack-api/": "https://deno.land/x/deno_slack_api@2.8.0/", 36 | "deno-slack-hub/": "https://deno.land/x/deno_slack_hub@3.0.0/", 37 | "@std/assert": "jsr:@std/assert@^1.0.13", 38 | "@std/testing": "jsr:@std/testing@^1.0.13" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /manifest.ts: -------------------------------------------------------------------------------- 1 | import { Manifest } from "deno-slack-sdk/mod.ts"; 2 | 3 | // Messaging/* 4 | import ChannelMessageWorkflow from "./Messaging/workflows/channel_message.ts"; 5 | import EphemeralMessageWorkflow from "./Messaging/workflows/ephemeral_message.ts"; 6 | import DirectMessageWorkflow from "./Messaging/workflows/direct_message.ts"; 7 | 8 | // Built-in_Forms/* 9 | import FormDemoWorkflow from "./Built-in_Forms/workflows/form_demo.ts"; 10 | 11 | // Connectors/* 12 | import GiphyConnectorWorkflow from "./Connectors/workflows/giphy.ts"; 13 | import GoogleCalendarConnectorWorkflow from "./Connectors/workflows/google_calendar.ts"; 14 | 15 | // Custom_Functions/* 16 | import MySendMessageWorflow from "./Custom_Functions/workflows/my_send_message_workflow.ts"; 17 | 18 | // External_API_Calls/* 19 | import HttpbinWorkflow from "./External_API_Calls/workflows/ephemeral_message.ts"; 20 | 21 | // Datastores/* 22 | import TaskManagerWorkflow from "./Datastores/workflows/task_manager.ts"; 23 | import PTOWorkflow from "./Datastores/workflows/pto.ts"; 24 | import Tasks from "./Datastores/datastores/tasks.ts"; 25 | import PTOs from "./Datastores/datastores/pto.ts"; 26 | 27 | // Event_Triggers/* 28 | import MessageToChannelCreatorWorkflow from "./Event_Triggers/workflows/message_to_channel_creator.ts"; 29 | import ReplyToReactionWorkflow from "./Event_Triggers/workflows/reply_to_reaction.ts"; 30 | import PingPongMessageWorkflow from "./Event_Triggers/workflows/ping_pong_message.ts"; 31 | 32 | // Scheduled Triggers/* 33 | import ScheduledWorkflow from "./Scheduled_Triggers/workflows/do_nothing.ts"; 34 | 35 | // Button_Interactions/* 36 | import InteractiveBlocksModalDemoWorkflow from "./Button_Interactions/workflows/interactive_blocks_demo.ts"; 37 | import BlockKitButtonDemoWorkflow from "./Button_Interactions/workflows/block_kit_button_demo.ts"; 38 | 39 | // Block_Kit_Modals/* 40 | import BlockKitModalDemoWorkflow from "./Block_Kit_Modals/workflows/block_kit_modal_demo.ts"; 41 | 42 | // Canvas Triggers/* 43 | import CanvasCreateWorkflow from "./Canvases/workflows/create_canvas.ts"; 44 | import CanvasUpdateWorkflow from "./Canvases/workflows/update_canvas.ts"; 45 | import CanvasShareWorkflow from "./Canvases/workflows/share_canvas.ts"; 46 | import CanvasCopyWorkflow from "./Canvases/workflows/copy_canvas.ts"; 47 | 48 | /** 49 | * The app manifest contains the app's configuration. This 50 | * file defines attributes like app name and description. 51 | * https://api.slack.com/automation/manifest 52 | */ 53 | export default Manifest({ 54 | name: "deno-code-snippets", 55 | description: "A collection of Slack's next-gen platform feature demos", 56 | icon: "assets/default_new_app_icon.png", 57 | workflows: [ 58 | ChannelMessageWorkflow, 59 | EphemeralMessageWorkflow, 60 | DirectMessageWorkflow, 61 | FormDemoWorkflow, 62 | GiphyConnectorWorkflow, 63 | GoogleCalendarConnectorWorkflow, 64 | MySendMessageWorflow, 65 | HttpbinWorkflow, 66 | TaskManagerWorkflow, 67 | PTOWorkflow, 68 | MessageToChannelCreatorWorkflow, 69 | ReplyToReactionWorkflow, 70 | PingPongMessageWorkflow, 71 | ScheduledWorkflow, 72 | InteractiveBlocksModalDemoWorkflow, 73 | BlockKitButtonDemoWorkflow, 74 | BlockKitModalDemoWorkflow, 75 | CanvasCreateWorkflow, 76 | CanvasUpdateWorkflow, 77 | CanvasShareWorkflow, 78 | CanvasCopyWorkflow, 79 | ], 80 | outgoingDomains: [ 81 | "httpbin.org", // for External_API_Calls/functions/* 82 | ], 83 | datastores: [ 84 | Tasks, // for Datastores/* 85 | PTOs, // for Datastores/* 86 | ], 87 | features: { 88 | appHome: { 89 | messagesTabEnabled: true, 90 | messagesTabReadOnlyEnabled: false, 91 | }, 92 | }, 93 | botScopes: [ 94 | "commands", 95 | "chat:write", 96 | "chat:write.public", 97 | "groups:write", // for Event_Triggers's ShareCanvasWorkflow 98 | "im:write", // for Event_Triggers's ShareCanvasWorkflow 99 | "channels:read", // for Event_Triggers's MessageToChannelCreatorWorkflow 100 | "reactions:read", // for Event_Triggers's ReplyToReactionWorkflow 101 | "channels:history", // for Event_Triggers's PingPongMessageWorkflow 102 | "datastore:read", // for Datastores/* 103 | "datastore:write", // for Datastores/* 104 | "canvases:read", // for Canvases/* 105 | "canvases:write", // for Canvases/* 106 | ], 107 | }); 108 | --------------------------------------------------------------------------------