Edits history of script submission #23498 for ' Slack Events API handler (HTTP route template) (slack)'

  • bunnative
    One script reply has been approved by the moderators
    Ap­pro­ved
    //native
    
    // Template for receiving Slack Events API callbacks on a Windmill HTTP route.
    //
    // Setup:
    // 1. Save this script, then add an HTTP route trigger on it: method POST, request type
    //    Sync (Slack needs the challenge answered in the response), authentication
    //    "Signature" with the Slack preset and your app's Signing Secret
    //    (api.slack.com/apps → your app → Basic Information → App Credentials).
    //    Windmill then rejects any request Slack did not sign; this script never sees it.
    // 2. In the Slack app, open Event Subscriptions, turn it on and paste the route URL as
    //    the Request URL. Slack sends a url_verification challenge, which main() answers.
    // 3. Subscribe to bot events and add the scope each one needs, then reinstall the app:
    //      message.channels       → channels:history   (the bot must be in the channel)
    //      reaction_added         → reactions:read
    //      app_mention            → app_mentions:read
    //      member_joined_channel  → channels:read
    //    Saving the events in the Slack UI adds missing scopes for you (it also adds
    //    groups:read and mpim:read for member_joined_channel). Or paste this into the app manifest (App Manifest → YAML), merged with what is there:
    //
    //      oauth_config:
    //        scopes:
    //          bot:
    //            - app_mentions:read
    //            - channels:history
    //            - channels:read
    //            - reactions:read
    //      settings:
    //        event_subscriptions:
    //          request_url: https://<your-windmill>/api/r/<route_path>
    //          bot_events:
    //            - app_mention
    //            - member_joined_channel
    //            - message.channels
    //            - reaction_added
    //
    // Slack waits 3 seconds for a 2xx and retries up to 3 times otherwise. Keep the handlers
    // below short, or hand long work to another job (wmill.runScriptByPathAsync) and return.
    // This script is //native so it starts in milliseconds; that needs a native worker,
    // which the default deployment includes.
    
    // Runs before main() on trigger calls: keeps the body and the two retry headers.
    export async function preprocessor(event: {
      kind: string
      body: { [key: string]: any }
      headers: { [key: string]: string }
    }) {
      if (event.kind !== "http") {
        throw new Error(`Expected an HTTP route trigger, got ${event.kind}`)
      }
      return {
        body: event.body,
        retry_num: event.headers["x-slack-retry-num"] ?? "",
        retry_reason: event.headers["x-slack-retry-reason"] ?? "",
      }
    }
    
    /**
     * Slack Events API handler (HTTP route template)
     * Answers Slack's url_verification challenge, then dispatches message, reaction_added, app_mention and member_joined_channel events, skipping bot echoes and timeout retries.
     */
    export async function main(
      body: { [key: string]: any },
      retry_num: string | undefined,
      retry_reason: string | undefined
    ) {
      if (body.type === "url_verification") {
        return { challenge: body.challenge }
      }
    
      // A timeout retry means the first delivery reached Windmill and its job is queued or
      // running, so handling it again would duplicate the work. Other retry reasons
      // (connection or HTTP errors) mean the first delivery was lost: let those through.
      if (retry_num && retry_reason === "http_timeout") {
        return { skipped: "retry", retry_num }
      }
    
      if (body.type !== "event_callback") {
        return { skipped: body.type }
      }
    
      const event = body.event
      // Messages posted through any app, this one included (even with a user token):
      // without this a bot that replies triggers itself in a loop.
      if (event.bot_id) {
        return { skipped: "bot" }
      }
    
      switch (event.type) {
        case "message":
          // Edits, deletions and joins arrive as message events with a subtype.
          if (event.subtype) {
            return { skipped: `message.${event.subtype}` }
          }
          return {
            type: "message",
            channel: event.channel,
            user: event.user,
            text: event.text,
            ts: event.ts,
            thread_ts: event.thread_ts,
          }
        case "app_mention":
          // With message.channels also subscribed, a mention arrives twice: once as
          // app_mention and once as message. React to one of them only.
          return {
            type: "app_mention",
            channel: event.channel,
            user: event.user,
            text: event.text,
            ts: event.ts,
            thread_ts: event.thread_ts,
          }
        case "reaction_added":
          return {
            type: "reaction_added",
            reaction: event.reaction,
            user: event.user,
            channel: event.item?.channel,
            ts: event.item?.ts,
          }
        case "member_joined_channel":
          return {
            type: "member_joined_channel",
            channel: event.channel,
            user: event.user,
            inviter: event.inviter,
          }
        default:
          return { skipped: event.type }
      }
    }
    

    Submitted by hugo989 3 hours ago