Deno Slack SDK

repository·main·Indexed 42 days ago

https://github.com/slackapi/deno-slack-sdk

A Deno SDK for building Slack apps using platform features such as custom functions, workflows, triggers, and datastores. It provides tools for defining app manifests, implementing Block Kit action handlers via addBlockActionsHandler(), and managing custom events with DefineEvent.

Tokens
17K
Snippets
48
Records
59
Agent score
52%

What's inside Deno Slack SDK

  1. Interactivity Handlers for custom functions

    main

    If your function creates messages or opens views, you can define interactivity handlers to respond to user actions. Run-on-Slack applications support:

    • Block Kit Action Handlers: Handle events from interactive Block Kit components like Buttons, Menus, and Date/Time Pickers.
    • View Handlers: Handle events triggered from Modals (which are composed of Views).
    • Block Kit Suggestion Handlers: Handle events from external-data-sourced Block Kit select menus.
  2. Implement Block Kit suggestion handlers for dynamic menus

    main

    Use addBlockSuggestionHandler() to provide dynamic, external-data-sourced options for Block Kit drop-down menus (specifically those with type: "external_select").

    This allows your function to respond to a user opening a menu by fetching data from an external API and returning a list of options.

    Key Workflow:

    1. Define a Function: Create a standard Slack function using DefineFunction.
    2. Post a Message: In your function logic, use client.chat.postMessage to send a Block Kit message containing an element with type: "external_select". Ensure you set a unique action_id for that element.
    3. Register the Handler: Chain .addBlockSuggestionHandler() to your SlackFunction definition. The handler receives a context containing the body and client, and must return an object with an options array.
    4. Handle Completion: If your function is waiting for user interaction (like a button click following a selection), return { completed: false } in your initial function execution.
    import { SlackFunction } from "deno-slack-sdk/mod.ts";
    import { QuoteFunction } from "./definition.ts";
    
    export default SlackFunction(QuoteFunction, async ({ inputs, client }) => {
      await client.chat.postMessage({
        channel: inputs.channel_id,
        blocks: [{
          "type": "actions",
          "block_id": "so-inspired",
          "elements": [{
            type: "external_select",
            placeholder: { type: "plain_text", text: "Inspire" },
            action_id: "ext_select_input",
          }, {
            type: "button",
            text: { type: "plain_text", text: "Post" },
            action_id: "post_quote",
          }],
        }],
      });
    
      return {
        completed: false,
      };
    }).addBlockSuggestionHandler(
      "ext_select_input",
      async ({ body, client }) => {
        const apiResp = await fetch("https://motivational-quote-api.herokuapp.com/quotes");
        const quotes = await apiResp.json();
        
        return {
          "options": quotes.map((q) => ({
            value: `${q.id}`,
            text: { type: "plain_text", text: q.quote.slice(0, 70) },
          })),
        };
      },
    );
  3. Create a new Slack app project

    main

    You can initialize a new project using a template by running the slack create command. This creates a directory containing a manifest.ts file, which serves as the central configuration for your app's name, description, functions, and workflows.

    slack create my-app --template slack-samples/deno-blank-template
    
    cd my-app/
  4. Define a Slack application manifest

    main

    A Manifest defines your entire Slack application, including its identity (name, description, icon), permissions (botScopes), and behavioral components like functions and datastores. You define a manifest by using the Manifest export and passing a configuration object of type SlackManifestType to it.

    import { Manifest } from "slack-cloud-sdk/mod.ts";
    import { ReverseString } from "./functions/reverse_definition.ts";
    
    export default Manifest({
      name: "heuristic-tortoise-312",
      description: "A demo showing how to use Slack functions",
      icon: "assets/icon.png",
      botScopes: ["commands", "chat:write", "chat:write.public"],
      functions: [ReverseString],
      datastores: [],
      outgoing_domains: [],
    });
  5. Register a Datastore to your App

    main

    To make a Datastore available to your application, include it in the datastores array when calling the Manifest function.

    Note: Registering a Datastore automatically adds the datastore:read and datastore:write scopes to your app's botScopes.

    export default Manifest({
      name: "admiring-ox-50",
      description: "Reverse a string",
      icon: "assets/icon.png",
      functions: [ReverseFunction],
      outgoingDomains: [],
      datastores: [ReversalsDatastore],
      botScopes: [
        "commands",
        "chat:write",
        "chat:write.public",
        "datastore:read",
        "datastore:write",
      ],
    });
  6. Run your Slack app

    main
    To run your app locally and test it, use the slack run command. During the process, the CLI may prompt you to create or confirm triggers (like the triggers/hello_world.ts file). Once running, you can interact with your app via the triggers you've defined in Slack.
    slack run
  7. Generate Slack function source files

    main

    Use the ./generate script to automatically create TypeScript source files and test files for Slack functions. The script generates files in the schema/slack/functions directory (e.g., schema/slack/functions/function_name.ts and schema/slack/functions/function_name_test.ts) and automatically updates schema/slack/functions/mod.ts to include the new imports/exports.

    Note: The script removes outdated function TypeScript files but does not remove their corresponding test files; you must delete outdated test files manually. If the script completes without linter errors, the generated files are formatted and linted according to project standards.

    ./generate
  8. Register custom events in the Manifest

    main

    All custom events must be explicitly registered in your App's Manifest to be usable. There is no automated registration. Add your event definitions to the events array within the Manifest function call.

    Manifest({
      ... 
      events: [IncidentEvent],
    });
  9. Add view submission and closed handlers

    main

    You can register handlers to respond to specific view interactions by chaining methods onto your SlackFunction definition. These handlers use a constraint to match the view's callback_id.

    Supported Handlers

    • addViewSubmissionHandler(constraint, handler): Invoked when a user submits a view.
    • addViewClosedHandler(constraint, handler): Invoked when a user closes a view. Requirement: The view must have notify_on_close: true set.

    Constraint Types

    The constraint argument can be:

    • A string: Must match the callback_id exactly.
    • An array of strings: Matches if the callback_id is in the array.
    • A RegExp: Matches if the callback_id satisfies the regular expression.

    Response Actions

    Inside a view handler, you can respond to interactions in two ways:

    1. Using the Client API: Call await client.views.push(...) or await client.views.update(...).
    2. Using response_action: Return a specially crafted object from the handler function.

    Comparison of methods:

    // Method 1: Using the API
    }).addViewSubmissionHandler(/view/, async ({ client, body }) => {
      await client.views.push({
        trigger_id: body.trigger_id,
        view: { /* ... */ },
      });
    })
    
    // Method 2: Using response_action
    }).addSubmissionHandler(/view/, async () => {
      return {
        response_action: "push",
        view: { /* ... */ },
      };
    });
    export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
      // ... logic ...
    }).addViewSubmissionHandler(
      /view/, 
      async ({ view, body, token }) => {
        console.log("Incoming view submission handler invocation", body);
      },
    )
      .addViewClosedHandler(
        /view/,
        async ({ view, body, token }) => {
          console.log("Incoming view closed handler invocation", body);
        },
      );
  10. Implement runtime logic with `SlackFunction`

    main

    To add logic to your function, create the file specified in source_file and use export default SlackFunction(...). The SlackFunction wrapper takes your DefineFunction definition and a handler function.

    Function Handler Context

    The handler receives a context object containing:

    • inputs: The input parameters defined in your function.
    • env: Environment variables.
    • client: An API client instance from deno-slack-api.
    • token: The application's access token.
    • team_id: The Slack workspace ID.
    • enterprise_id: The enterprise ID (empty string if not in an enterprise grid).
    • event: The full incoming event details.

    Function Return Object

    The handler must return an object containing:

    • outputs: An object matching the structure of your output_parameters. This is required unless an error is returned.
    • error: (Optional) A string indicating an error. If present, the function fails regardless of outputs.
    • completed: (Optional) A boolean indicating completion (defaults to true).
    import { SlackFunction } from "deno-slack-sdk/mod.ts";
    
    export default SlackFunction(
      // Pass along the function definition you created earlier using `DefineFunction`
      DinoFunction,
      ({ inputs }) => {
        // Implement your function
        const { name } = inputs;
        const dinoname = `${name}asaurus`;
    
        // Don't forget any required output parameters
        return { outputs: { dinoname } };
      },
    );
  11. Post messages with custom event metadata

    main

    When posting a message to Slack, you can include an event_type and event_payload within the metadata parameter. If the event_type matches a custom event name in your manifest, Slack validates the payload against that event's schema.

    Note: If validation fails, a warning is returned and the message is still posted, but the metadata is dropped.

    // Example: Using metadata in a workflow step
    MyWorkflow.addStep(Schema.slack.functions.SendMessage, {
      channel_id: MyWorkflow.inputs.channel_id,
      message: "We have an incident!",
      metadata: {
        event_type: IncidentEvent,
        event_payload: {
          id: MyWorkflow.inputs.incident_id,
          title: MyWorkflow.inputs.incident_title,
          summary: MyWorkflow.inputs.incident_summary,
          severity: MyWorkflow.inputs.incident_severity,
          date_created: MyWorkflow.inputs.incident_date,
        },
      },
    });
    
    // Example: Using metadata at function runtime via the client
    await client.chat.postMessage({
      channel_id: inputs.channel_id,
      message: "We have an incident!",
      metadata: {
        event_type: IncidentEvent,
        event_payload: {
          id: inputs.incident_id,
          title: inputs.incident_title,
          summary: inputs.incident_summary,
          severity: inputs.incident_severity,
          date_created: inputs.incident_date,
        },
      },
    });