UNPKG

@slack/bolt

Version:

A framework for building Slack apps, fast.

319 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.directMention = exports.subtype = exports.ignoreSelf = exports.matchEventType = exports.matchCommandName = exports.matchMessage = exports.matchConstraints = exports.autoAcknowledge = exports.onlyViewActions = exports.onlyEvents = exports.onlyOptions = exports.onlyCommands = exports.onlyShortcuts = exports.onlyActions = exports.isSlackEventMiddlewareArgsOptions = void 0; const errors_1 = require("../errors"); /** Type predicate that can narrow payloads block action or suggestion payloads */ function isBlockPayload(payload) { return 'action_id' in payload && payload.action_id !== undefined; } // TODO: consider exporting these type guards for use elsewhere within bolt // TODO: is there overlap with `function_executed` event here? function isCallbackIdentifiedBody(body) { return 'callback_id' in body && body.callback_id !== undefined; } // TODO: clarify terminology used internally: event vs. body vs. payload /** Type predicate that can narrow event bodies to ones containing Views */ function isViewBody(body) { return 'view' in body && body.view !== undefined; } function isEventArgs(args) { return 'event' in args && args.event !== undefined; } function isMessageEventArgs(args) { return isEventArgs(args) && 'message' in args; } function isSlackEventMiddlewareArgsOptions(optionOrListener) { return typeof optionOrListener !== 'function' && 'autoAcknowledge' in optionOrListener; } exports.isSlackEventMiddlewareArgsOptions = isSlackEventMiddlewareArgsOptions; /** * Middleware that filters out any event that isn't an action */ const onlyActions = async (args) => { if ('action' in args && args.action) { await args.next(); } }; exports.onlyActions = onlyActions; /** * Middleware that filters out any event that isn't a shortcut */ const onlyShortcuts = async (args) => { if ('shortcut' in args && args.shortcut) { await args.next(); } }; exports.onlyShortcuts = onlyShortcuts; /** * Middleware that filters out any event that isn't a command */ const onlyCommands = async (args) => { if ('command' in args && args.command) { await args.next(); } }; exports.onlyCommands = onlyCommands; /** * Middleware that filters out any event that isn't an options */ const onlyOptions = async (args) => { if ('options' in args && args.options) { await args.next(); } }; exports.onlyOptions = onlyOptions; // TODO: event terminology here "event that isn't an event" wat /** * Middleware that filters out any event that isn't an event */ const onlyEvents = async (args) => { if (isEventArgs(args)) { await args.next(); } }; exports.onlyEvents = onlyEvents; // TODO: event terminology "ViewAction" is confusing since "Action" we use for block actions /** * Middleware that filters out any event that isn't a view_submission or view_closed event */ const onlyViewActions = async (args) => { if ('view' in args) { await args.next(); } }; exports.onlyViewActions = onlyViewActions; /** * Middleware that auto acknowledges the request received */ const autoAcknowledge = async (args) => { if ('ack' in args && args.ack !== undefined) { await args.ack(); } await args.next(); }; exports.autoAcknowledge = autoAcknowledge; /** * Middleware that checks for matches given constraints */ function matchConstraints(constraints) { return async ({ payload, body, next, context }) => { // TODO: is putting matches in an array actually helpful? there's no way to know which of the regexps contributed // which matches (and in which order) let tempMatches; // Narrow type for ActionConstraints if ('block_id' in constraints || 'action_id' in constraints) { if (!isBlockPayload(payload)) { return; } // Check block_id if (constraints.block_id !== undefined) { if (typeof constraints.block_id === 'string') { if (payload.block_id !== constraints.block_id) { return; } } else { tempMatches = payload.block_id.match(constraints.block_id); if (tempMatches !== null) { context.blockIdMatches = tempMatches; } else { return; } } } // Check action_id if (constraints.action_id !== undefined) { if (typeof constraints.action_id === 'string') { if (payload.action_id !== constraints.action_id) { return; } } else { tempMatches = payload.action_id.match(constraints.action_id); if (tempMatches !== null) { context.actionIdMatches = tempMatches; } else { return; } } } } // Check callback_id if ('callback_id' in constraints && constraints.callback_id !== undefined) { let callbackId = ''; if (isViewBody(body)) { callbackId = body.view.callback_id; } else if (isCallbackIdentifiedBody(body)) { callbackId = body.callback_id; } else { return; } if (typeof constraints.callback_id === 'string') { if (callbackId !== constraints.callback_id) { return; } } else { tempMatches = callbackId.match(constraints.callback_id); if (tempMatches !== null) { context.callbackIdMatches = tempMatches; } else { return; } } } // Check type if ('type' in constraints) { if (body.type !== constraints.type) return; } await next(); }; } exports.matchConstraints = matchConstraints; /* * Middleware that filters out messages that don't match pattern */ function matchMessage(pattern) { return async ({ event, context, next }) => { let tempMatches; if (!('text' in event) || event.text === undefined) { return; } // Filter out messages or app mentions that don't contain the pattern if (typeof pattern === 'string') { if (!event.text.includes(pattern)) { return; } } else { tempMatches = event.text.match(pattern); if (tempMatches !== null) { context.matches = tempMatches; } else { return; } } await next(); }; } exports.matchMessage = matchMessage; /** * Middleware that filters out any command that doesn't match the pattern */ function matchCommandName(pattern) { return async ({ command, next }) => { // Filter out any commands that do not match the correct command name or pattern if (!matchesPattern(pattern, command.command)) { return; } await next(); }; } exports.matchCommandName = matchCommandName; function matchesPattern(pattern, candidate) { if (typeof pattern === 'string') { return pattern === candidate; } return pattern.test(candidate); } /* * Middleware that filters out events that don't match pattern */ function matchEventType(pattern) { return async ({ event, context, next }) => { let tempMatches; if (!('type' in event) || event.type === undefined) { return; } // Filter out events that don't contain the pattern if (typeof pattern === 'string') { if (event.type !== pattern) { return; } } else { tempMatches = event.type.match(pattern); if (tempMatches !== null) { context.matches = tempMatches; } else { return; } } await next(); }; } exports.matchEventType = matchEventType; /** * Filters out any event originating from the handling app. */ const ignoreSelf = async (args) => { const { botId, botUserId } = args.context; if (isEventArgs(args)) { if (isMessageEventArgs(args)) { const { message } = args; // Look for an event that is identified as a bot message from the same bot ID as this app, and return to skip if (message.subtype === 'bot_message' && message.bot_id === botId) { return; } } // It's an Events API event that isn't of type message, but the user ID might match our own app. Filter these out. // However, some events still must be fired, because they can make sense. const eventsWhichShouldBeKept = ['member_joined_channel', 'member_left_channel']; if (botUserId !== undefined && 'user' in args.event && args.event.user === botUserId && !eventsWhichShouldBeKept.includes(args.event.type)) { return; } } // If all the previous checks didn't skip this message, then its okay to resume to next await args.next(); }; exports.ignoreSelf = ignoreSelf; // TODO: breaking change: constrain the subtype argument to be a valid message subtype /** * Filters out any message events whose subtype does not match the provided subtype. */ function subtype(subtype1) { return async ({ message, next }) => { if (message && message.subtype === subtype1) { await next(); } }; } exports.subtype = subtype; const slackLink = /<(?<type>[@#!])?(?<link>[^>|]+)(?:\|(?<label>[^>]+))?>/; /** * Filters out any message event whose text does not start with an @-mention of the handling app. */ const directMention = async ({ message, context, next }) => { // When context does not have a botUserId in it, then this middleware cannot perform its job. Bail immediately. if (context.botUserId === undefined) { throw new errors_1.ContextMissingPropertyError('botUserId', 'Cannot match direct mentions of the app without a bot user ID. Ensure authorize callback returns a botUserId.'); } if (!message || !('text' in message) || message.text === undefined) { return; } // Match the message text with a user mention format const text = message.text.trim(); const matches = slackLink.exec(text); if (matches === null || // stop when no matches are found matches.index !== 0 || // stop if match isn't at the beginning // stop if match isn't a user mention with the right user ID matches.groups === undefined || matches.groups.type !== '@' || matches.groups.link !== context.botUserId) { return; } await next(); }; exports.directMention = directMention; //# sourceMappingURL=builtin.js.map