diff --git a/app.js b/app.js index 11b697c..08ab226 100644 --- a/app.js +++ b/app.js @@ -5,18 +5,19 @@ const axios = require('axios') const modules = require('./modules') const { verifySlack } = require('./modules/middleware') +const { bdayInteractive } = require('./modules/bday-interactive') +const { lambda, getFuncName } = require('./modules/util') +const { DEPLOYED } = process.env const app = express() -const { updateTask } = require('@eqworks/avail-bot') - const rawBodyBuffer = (req, _, buf, encoding) => { if (buf && buf.length) { req.rawBody = buf.toString(encoding || 'utf8') } } -app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true })) +app.use(bodyParser.urlencoded({ verify: rawBodyBuffer, extended: true })) app.use(bodyParser.json({ verify: rawBodyBuffer })) app.get('/', (_, res, next) => { @@ -83,37 +84,76 @@ e.g. */ app.use('/interactive', (req, res) => { const parsed = JSON.parse(req.body.payload) - const { - // type, - response_url, - // user, - // channel: { id, name }, - // TODO when would this be .length > 1 - actions: [{ - value: action_value, - // style, - // type: action_type, - // text: { text }, - }], + const { + type, + view: { + id, + hash, + callback_id, + private_metadata, + state: { values }, // only available from submission + blocks, + }, + actions = [], + user: { id: sender }, + // much more avb inside, check documentation } = parsed - // value standard: '[module] // param-1 // ...param-N' - const [module, taskId, customFieldId, value] = action_value.split(' // ') - if (module === 'vacay') { - updateTask(taskId, { custom_fields: { [customFieldId]: value }}) - .then(res => { - const { assignee: { name }, start_on, due_on, custom_fields } = res - const status = custom_fields.find(o => o.name === 'Status').enum_value.name - const date = start_on ? `${start_on} - ${due_on}` : due_on - axios.post(response_url, { - text: `Updated *${name}'s* vacation on *${date}* to *${status}*`, - mrkdwn: true, - response_type: 'ephemeral', - replace_original: false, - }) + + if (callback_id === 'bday') { + // manipulate data received from submission + const { data = {}, errors = {} } = bdayInteractive({ type, values }) + if (Object.values(errors).length) { + return res.status(200).json({ response_action: 'errors', errors }) + } + const { + ref, + response_url, + command, + channel_id, + } = JSON.parse(private_metadata) + + const payload = { + ref, + command, + type, + view_id: id, + hash, + response_url, + data, + blocks, + action: actions[0] || [], + channel_id, + sender + } + + const { worker } = modules['bday'] + + if (DEPLOYED) { + lambda.invoke({ + FunctionName: getFuncName('slack'), + InvocationType: 'Event', + Payload: JSON.stringify({ type: 'bday', payload }), + }, (err) => { + if (err) { + console.error(err) + return res.status(200).json({ response_type: 'ephemeral', text: 'Failed to process bday command' }) + } + if (type === 'view_submission') { + return res.status(200).json({ 'response_action': 'clear' }) + } + return res.sendStatus(200) }) + } else { + worker(payload).catch(console.error) + if (type === 'view_submission') { + // needs to have only for modals submission + return res.status(200).json({ 'response_action': 'clear' }) + } + // modal interactions need to have acknowledgement + return res.sendStatus(200) + } } - return res.status(200) // mandatory response }) // catch-all error handler diff --git a/modules/bday-blocks.js b/modules/bday-blocks.js new file mode 100644 index 0000000..2bc3981 --- /dev/null +++ b/modules/bday-blocks.js @@ -0,0 +1,314 @@ +module.exports._blocks = (ref) => ([ + { type: 'divider' }, + { + label: { + 'type': 'plain_text', + 'text': 'Select Bday Person :balloon:', + 'emoji': true + }, + block_id: `bday_person_${ref}`, + type: 'input', + element: + { + 'action_id': 'input', + 'type': 'users_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Bday Person', + emoji: true, + }, + }, + }, + { + 'type': 'input', + 'block_id': `url_${ref}`, + 'label': { + 'type': 'plain_text', + 'text': 'Miro URL' + }, + 'element': { + 'type': 'plain_text_input', + 'action_id': 'input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'add here your card url' + } + } + }, +]) + +module.exports.customMessage = (ref) => ({ + 'type': 'input', + 'block_id': `message_${ref}`, + 'label': { + 'type': 'plain_text', + 'text': 'Bday Message' + }, + 'element': { + 'type': 'plain_text_input', + 'action_id': 'input', + initial_value: 'Hope you have a wonderful day and eat lots of cake on behalf of all of us! :birthday:', + 'multiline': true + }, + 'hint': { + 'type': 'plain_text', + 'text': 'This is the default message', + } +}) + +module.exports.button = { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'action_id': 'add', + 'style': 'primary', + 'text': { + 'type': 'plain_text', + 'text': 'Add more' + }, + }, + ], + 'block_id': 'manage_fields', +} + +// to be added inside an actions block +module.exports.removeButton = { + // 'type': 'actions', + // 'elements': [ + // { + 'type': 'button', + 'action_id': 'remove', + 'text': { + 'type': 'plain_text', + 'text': 'Remove' + }, + 'style': 'danger' + // } + // ], + // 'block_id': 'remove', +} + +module.exports.signMessage = ([{ fullName, url }, ...rest], sender) => { + let name = fullName + + let getClick = (name, url, type = 'text') => { + const message = `Click :point_right: <${url}|here> :point_left: to sign a card for ${name}!` + return type === 'text' + ? message + : { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': message + } + } + } + + let clickSectionText = [getClick(fullName, url)] + let clickSectionBlock = [getClick(fullName, url, 'block')] + let allCards = `${url}` + + if (rest.length) { + for (let { fullName, url } of rest) { + name += ` & ${fullName}` + clickSectionText.push(getClick(fullName, url)) + clickSectionBlock.push(getClick(fullName, url, 'block')) + allCards += `, ${url}` + } + } + + const text = [ + `:tada: Birthday Alert for ${name} :tada:`, + `*${name}'s* birthday is coming up soon! Take some time and leave a nice message for them to read. Thanks! :smile:`, + ...clickSectionText, + 'Instructions are found inside the card.', + ].join('\n') + + const blocks = [ + { + 'type': 'header', + 'text': { + 'type': 'plain_text', + 'text': `:tada: Birthday Alert for ${name} :tada:`, + 'emoji': true + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `*${name}*'s birthday is coming up soon! Take some time and leave a nice message for them to read. Instructions are found inside the card. Thanks! :smile:` + } + }, + ...clickSectionBlock, + { + 'type': 'context', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': `From <@${sender}> on behalf of EQ`, + } + ] + } + ] + + const confirmation = [ + { + 'type': 'header', + 'text': { + 'type': 'plain_text', + 'text': `The card has been sent to everyone except ${name} to be signed! :tada:`, + 'emoji': true + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': 'Thanks for spreading some love! :smile:' + } + }, + { + 'type': 'divider' + }, + { + 'type': 'context', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': `Card link(s): ${allCards}`, + } + ] + } + ] + return { text, blocks, confirmation } +} + +module.exports.sendText = ({ fullName, url, message, sender }) => ([ + `:tada: Happy Birthday ${fullName}! :tada:`, + message, + `Click :point_right: <${url}|here> :point_left: to see the birthday card!`, + `- From <@${sender}> on behalf of EQ` +].join('\n')) + +module.exports.sendBlocks = ({ fullName, url, message, sender }) => ([ + { + 'type': 'header', + 'text': { + 'type': 'plain_text', + 'text': `:tada: Happy Birthday ${fullName}! :tada:`, + 'emoji': true + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': message + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `Click :point_right: <${url}|here> :point_left: to see the birthday card!` + } + }, + { + 'type': 'divider' + }, + { + 'type': 'context', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': `From <@${sender}> on behalf of EQ`, + } + ] + } +]) + +module.exports.sendConfirmation = ([{ fullName, url }, ...rest]) => { + let name = fullName + let allCards = `${url}` + if (rest.length) { + for (let { fullName, url } of rest) { + name += ` & ${fullName}` + allCards += `, ${url}` + } + } + return [ + { + 'type': 'header', + 'text': { + 'type': 'plain_text', + 'text': `Card has been sent to ${name}! :tada:`, + 'emoji': true + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': 'Thanks for spreading some love! :smile:' + } + }, + { + 'type': 'divider' + }, + { + 'type': 'context', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': `Card link(s): ${allCards}`, + } + ] + }, + ] +} +module.exports.celebrateBlocks = (BDAY_USER_NAME, renderedText) => ([ + { + 'type': 'header', + 'text': { + 'type': 'plain_text', + 'text': ':tada: Birthday Alert :tada:', + 'emoji': true + } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': `@here It's @${BDAY_USER_NAME}'s birthday today! ${renderedText}` + } + }, + { + 'type': 'image', + 'image_url': 'https://media.giphy.com/media/IQF90tVlBIByw/giphy.gif', + 'alt_text': 'minion birthday' + } +]) + +module.exports.defaultBlock = [ + { type: 'divider' }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Oops! Missing some required keywords! Please see the following guide on how to use the */bday* command:' + }, + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: [ + '(1) */bday* sign', + '(2) */bday* send', + '(3) */bday* celebrate for @bday-user optional custom message', + ].join('\n'), + }, + }, +] diff --git a/modules/bday-interactive.js b/modules/bday-interactive.js new file mode 100644 index 0000000..84215cc --- /dev/null +++ b/modules/bday-interactive.js @@ -0,0 +1,48 @@ +module.exports.bdayInteractive = ({ type, values }) => { + + let data = {} + if (type === 'view_submission') { + /** + values = { + bday_person_1: { input: { type: 'users_select', selected_user: 'U01B87YFQ78' } }, + url_1: { input: { type: 'plain_text_input', value: 'test' } }, + bday_person_2: { input: { type: 'users_select', selected_user: 'U01B87YFQ78' } }, + url_2: { input: { type: 'plain_text_input', value: 'test' } } + ... + } + */ + const errors = {} + // TODO improve validation + const validData = (key, input) => { + if (!input.startsWith('http')) { + errors[key] = 'Invalid url. Hint: make sure it includes `http`' + } + } + + data = Object.entries(values).reduce((acc, [key, { input }]) => { + // key = bday_person_1 || url_1 || message_1 + const user_index = key.slice(-1) + if (acc[user_index]) { + if (key.includes('url')) { + validData(key, input.value) + acc[user_index] = { ...acc[user_index], url: input.value } + } + if (key.includes('message')) { + acc[user_index] = { ...acc[user_index], message: input.value } + } + } else { + acc[user_index] = { id: input.selected_user } + } + return acc + }, {}) + /** + * data { + '1': { id: 'U01B87YFQ78', url: 'test'}, + '2': { id: 'U01B87ZH3GW', url: 'test', message: 'bday wishes' }, + ... + } + */ + return { errors, data } + } + return data +} diff --git a/modules/bday.js b/modules/bday.js index 45a4267..a1b16f3 100644 --- a/modules/bday.js +++ b/modules/bday.js @@ -1,6 +1,18 @@ const axios = require('axios') const { WebClient } = require('@slack/web-api') -const { lambda, getFuncName } = require('./util') +const { lambda, getFuncName } = require('./util') +const { + _blocks, + button, + removeButton, + signMessage, + customMessage, + sendText, + sendBlocks, + sendConfirmation, + defaultBlock, + celebrateBlocks, +} = require('./bday-blocks') const GENERAL_CHANNEL_ID = 'C1FDR4QJF' @@ -38,216 +50,241 @@ const confirmChannel = (response_url, cmd, channel) => ( }) ) -const worker = async ({ command, channel_id, response_url, user_id }) => { +const worker = async ({ + command, + channel_id, + response_url, + trigger_id, + type, + view_id, + hash, + data, + blocks, + action, + ref, + sender, +}) => { // send card for members to sign - if (command.includes('sign') && command.includes('for')) { - // make sure to execute command from #general - if (channel_id !== GENERAL_CHANNEL_ID) { - const { channel: { name } } = await web.conversations.info({ channel: GENERAL_CHANNEL_ID }) - return confirmChannel(response_url, 'sign', name) - } - // sign URL for @user_id|user_name - const R = /<(?.*)> for <@(?.*)\|(?.*)>/ - const matches = command.match(R) - // notify user of invalid input - if (!matches) { - return invalidInputNotice(response_url, command) + if (command === 'sign') { + // first iteration returns modal + if (!type) { + // make sure to execute command from #general + if (channel_id !== GENERAL_CHANNEL_ID) { + const { channel: { name } } = await web.conversations.info({ channel: GENERAL_CHANNEL_ID }) + return confirmChannel(response_url, 'sign', name) + } + const state = { + response_url, + channel_id, + ref: 1, + command: 'sign', + } + return web.views.open({ + trigger_id, + view: { + 'callback_id': 'bday', + 'private_metadata': JSON.stringify(state), + 'type': 'modal', + 'title': { + 'type': 'plain_text', + 'text': 'Bday details' + }, + 'blocks': [..._blocks(state.ref), button], + 'close': { + 'type': 'plain_text', + 'text': 'Nevermind' + }, + 'submit': { + 'type': 'plain_text', + 'text': 'Send' + }, + } + }) } - // send to every member in #general excluding bday person - const { groups: { MIRO_URL, BDAY_USER_ID } } = matches - const bdayPerson = await web.users.info({ user: BDAY_USER_ID }) - const { user: { real_name: bdayPersonFullName } } = bdayPerson - const { members } = await web.conversations.members({ channel: channel_id }) - members.splice(members.findIndex((m) => m === BDAY_USER_ID), 1) - // return web.conversations.open({ users: members.toString() }) - return Promise.all( - members.map((channel) => web.chat.postMessage({ - channel, - text: [ - `:tada: Birthday Alert for ${bdayPersonFullName} :tada:`, - `*${bdayPersonFullName}'s* birthday is coming up soon! Take some time and leave a nice message for ${bdayPersonFullName} to read. Thanks! :smile:`, - `Click :point_right: <${MIRO_URL}|here> :point_left: to sign! Instructions are found inside the card.` - ].join('\n'), - blocks: [ - { - 'type': 'header', - 'text': { - 'type': 'plain_text', - 'text': `:tada: Birthday Alert for ${bdayPersonFullName} :tada:`, - 'emoji': true - } + + // when more fields are added + if (type === 'block_actions' && action.block_id === 'manage_fields') { + let updatedBlocks + const _buttons = blocks.pop() + + if (action.action_id === 'add') { + if (blocks.length === 3) { + _buttons.elements.push(removeButton) + } + updatedBlocks = [...blocks, ..._blocks(ref + 1), _buttons] + } else { + blocks.splice(-3) + if (blocks.length === 3) { + updatedBlocks = [...blocks, button] + } else { + updatedBlocks = [...blocks, _buttons] + } + } + const private_metadata = JSON.stringify({ ref: ref + 1, response_url, command, channel_id }) + return web.views.update({ + view_id, + hash, + view: { + private_metadata, + 'callback_id': 'bday', + 'type': 'modal', + 'title': { + 'type': 'plain_text', + 'text': 'Bday details' }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `*${bdayPersonFullName}*'s birthday is coming up soon! Take some time and leave a nice message for ${bdayPersonFullName} to read. Thanks! :smile:` - } + 'blocks': updatedBlocks, + 'close': { + 'type': 'plain_text', + 'text': 'Nevermind' }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `Click :point_right: <${MIRO_URL}|here> :point_left: to sign! Instructions are found inside the card.` - } - } - ] - })) - ) - .then(() => { + 'submit': { + 'type': 'plain_text', + 'text': 'Send' + }, + }, + }) + } + + // upon modal submission + if (type === 'view_submission') { + // send to every member in #general excluding bday person + const { members } = await web.conversations.members({ channel: channel_id }) + + for (let [user_index, { id }] of Object.entries(data)) { + members.splice(members.findIndex((m) => m === id), 1) + const { user: { real_name } } = await web.users.info({ user: id }) + data[user_index].fullName = real_name + } + + const bdayData = Object.values(data) + const { text, blocks, confirmation } = signMessage(bdayData, sender) + + // return web.conversations.open({ users: members.toString() }) + return Promise.all( + members.map((channel) => web.chat.postMessage({ + channel, + text, + blocks, + })) + ).then(() => { // notify user invitation has been sent return axios.post(response_url, { response_type: 'ephemeral', - blocks: [ - { - 'type': 'header', - 'text': { - 'type': 'plain_text', - 'text': `Card has been sent for signing to everyone except ${bdayPersonFullName}! :tada:`, - 'emoji': true - } - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': 'Thanks for spreading some love! :smile:' - } - }, - { - 'type': 'divider' - }, - { - 'type': 'context', - 'elements': [ - { - 'type': 'plain_text', - 'text': `Card link: ${MIRO_URL}`, - 'emoji': true - } - ] - } - - ] + blocks: confirmation }) - }) - .catch((e) => { + }).catch((e) => { return axios.post(response_url, { response_type: 'ephemeral', text: `Something went wrong: ${e}.`, }) }) + } } // send card to bday user - if (command.includes('send') && command.includes('to')) { - const R = /<(?.*)> to <@(?.*)\|(?.*)>/ - const RwithMessage = /<(?.*)> to <@(?.*)\|(?.*)> (?.*)/ - const matches = command.match(R) - const matchesWithOptMessage = command.match(RwithMessage) - - // notify user of invalid input - if (!matches) { - return invalidInputNotice(response_url, command) + if (command === 'send') { + // first iteration returns modal + if (!type) { + const state = { + response_url, + ref: 1, + command: 'send', + } + return web.views.open({ + trigger_id, + view: { + 'callback_id': 'bday', + 'private_metadata': JSON.stringify(state), + 'type': 'modal', + 'title': { + 'type': 'plain_text', + 'text': 'Send bday card' + }, + 'blocks': [..._blocks(state.ref), customMessage(state.ref), button], + 'close': { + 'type': 'plain_text', + 'text': 'Nevermind' + }, + 'submit': { + 'type': 'plain_text', + 'text': 'Send' + }, + } + }) } - // send to bday user - const { groups: { MIRO_URL, BDAY_USER_ID } } = matches - const sender = await web.users.info({ user: user_id }) - const bdayPerson = await web.users.info({ user: BDAY_USER_ID }) - const { user: { real_name: bdayPersonFullName } } = bdayPerson - const { user: { real_name: senderFullName } } = sender - const renderedText = (!matchesWithOptMessage) - ? 'Hope you have a wonderful day and eat lots of cake on behalf of all of us! :birthday:' - : matchesWithOptMessage['groups']['CUSTOM_MESSAGE'] - return web.chat.postMessage({ - channel: BDAY_USER_ID, - text: [ - `:tada: Happy Birthday ${bdayPersonFullName}! :tada:`, - renderedText, - `Click :point_right: <${MIRO_URL}|here> :point_left: to see the birthday card!`, - `- From ${senderFullName} on behalf of EQ` - ].join('\n'), - blocks: [ - { - 'type': 'header', - 'text': { + // when more fields are added + if (type === 'block_actions' && action.block_id === 'manage_fields') { + let updatedBlocks + const _buttons = blocks.pop() + + if (action.action_id === 'add') { + if (blocks.length === 4) { + _buttons.elements.push(removeButton) + } + updatedBlocks = [...blocks, ..._blocks(ref + 1), customMessage(ref + 1), _buttons] + } else { + blocks.splice(-4) + if (blocks.length === 4) { + updatedBlocks = [...blocks, button] + } else { + updatedBlocks = [...blocks, _buttons] + } + } + const private_metadata = JSON.stringify({ ref: ref + 1, response_url, command }) + return web.views.update({ + view_id, + hash, + view: { + private_metadata, + 'callback_id': 'bday', + 'type': 'modal', + 'title': { 'type': 'plain_text', - 'text': `:tada: Happy Birthday ${bdayPersonFullName}! :tada:`, - 'emoji': true - } - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': renderedText - } - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `Click :point_right: <${MIRO_URL}|here> :point_left: to see the birthday card!` - } - }, - { - 'type': 'divider' + 'text': 'Send bday card' + }, + 'blocks': updatedBlocks, + 'close': { + 'type': 'plain_text', + 'text': 'Nevermind' + }, + 'submit': { + 'type': 'plain_text', + 'text': 'Send' + }, }, - { - 'type': 'context', - 'elements': [ - { - 'type': 'plain_text', - 'text': `From ${senderFullName} on behalf of EQ`, - 'emoji': true - } - ] - } - ] - }) - .then(() => { - // notify user bday card has been sent + }) + } + + // upon modal submission + if (type === 'view_submission') { + for (let [user_index, { id }] of Object.entries(data)) { + const { user: { real_name } } = await web.users.info({ user: id }) + data[user_index].fullName = real_name + } + + const bdayData = Object.values(data) + + // send to bday user + return Promise.all( + bdayData.map(({ id, url, fullName, message }) => web.chat.postMessage({ + channel: id, + text: sendText({ url, fullName, message, sender }), + blocks: sendBlocks({ url, fullName, message, sender }), + })) + ).then(() => { + // notify user invitation has been sent return axios.post(response_url, { response_type: 'ephemeral', - blocks: [ - { - 'type': 'header', - 'text': { - 'type': 'plain_text', - 'text': `Card has been sent to ${bdayPersonFullName}! :tada:`, - 'emoji': true - } - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': 'Thanks for spreading some love! :smile:' - } - }, - { - 'type': 'divider' - }, - { - 'type': 'context', - 'elements': [ - { - 'type': 'plain_text', - 'text': `Card link: ${MIRO_URL}`, - 'emoji': true - } - ] - } - ], + blocks: sendConfirmation(bdayData) }) - }) - .catch((e) => { + }).catch((e) => { return axios.post(response_url, { response_type: 'ephemeral', text: `Something went wrong: ${e}.`, }) }) + } } // announce in channel @@ -278,60 +315,26 @@ const worker = async ({ command, channel_id, response_url, user_id }) => { `@here It's @${BDAY_USER_NAME}'s birthday today!`, renderedText, ].join('\n'), - blocks: [ - { - 'type': 'header', - 'text': { - 'type': 'plain_text', - 'text': ':tada: Birthday Alert :tada:', - 'emoji': true - } - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': `@here It's @${BDAY_USER_NAME}'s birthday today! ${renderedText}` - } - }, - { - 'type': 'image', - 'image_url': 'https://media.giphy.com/media/IQF90tVlBIByw/giphy.gif', - 'alt_text': 'minion birthday' - } - ] + blocks: celebrateBlocks(BDAY_USER_NAME, renderedText), }) } // default return for missing params return axios.post(response_url, { response_type: 'ephemeral', - blocks: [ - { type: 'divider' }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Oops! Missing some required keywords! Please see the following guide on how to use the */bday* command:' - }, - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: [ - '(1) */bday* sign for @bday-user', - '(2) */bday* send to @bday-user', - '(3) */bday* celebrate for @bday-user', - ].join('\n'), - }, - }, - ], + blocks: defaultBlock, }) } const route = (req, res) => { - const { text = '', response_url, channel_id, user_id } = req.body + const { + text = '', + response_url, + channel_id, + user_id, + trigger_id, + } = req.body + const validCmd = text.match(/sign|send|celebrate/) if (text !== '' && !validCmd) { return res.status(200).json({ @@ -340,7 +343,7 @@ const route = (req, res) => { }) } - const payload = { command: text, response_url, channel_id, user_id } + const payload = { 'command': text, response_url, channel_id, sender: user_id, trigger_id } if (DEPLOYED) { lambda.invoke({ @@ -352,15 +355,27 @@ const route = (req, res) => { console.error(err) return res.status(200).json({ response_type: 'ephemeral', text: 'Failed to process bday command' }) } - return res.status(200).json({ response_type: 'ephemeral', text: 'Got it! Processing your bday commands now...' }) + if (text.match(/sign|send/)) { + return res.status(200).json({ + response_type: 'ephemeral', + text: 'Got it! But I will need more details...' + }) + } else { + // celebrate doens't need more details yet + return res.sendStatus(200) + } }) } else { worker(payload).catch(console.error) - return res.status(200).json({ - response_type: 'ephemeral', - text: 'Got it! Processing your bday commands now...' - }) + if (text.match(/sign|send/)) { + return res.status(200).json({ + response_type: 'ephemeral', + text: 'Got it! But I will need more details...' + }) + } else { + // celebrate doens't need more details yet + return res.sendStatus(200) + } } } - module.exports = { worker, route } diff --git a/yarn.lock b/yarn.lock index 54dd4e2..d632d50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -126,26 +126,25 @@ dependencies: "@types/node" ">=8.9.0" -"@slack/types@^1.2.1": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.3.0.tgz#ea71916449ce7cb79fc57ce756743abfeba0f752" - integrity sha512-3AjHsDJjJKT3q0hQzFHQN7piYIh99LuN7Po56W/R6P/uscqZqwS5xm1U1cTYGIzk8fmsuW7TvWVg0W85hKY/MQ== +"@slack/types@^1.7.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a" + integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg== "@slack/web-api@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.7.0.tgz#173816e26dbae06b749741164f240186a0b44d64" - integrity sha512-n3c2S5+fvRJV/FYhdZXPuMrRhnEdF5yPhSXK9ph4xJezNzMbb4OWymrMz7+XVf4qOz30HxN9A6ktV98stOBrhw== + version "5.13.0" + resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-5.13.0.tgz#44b3c744f8f2c75b188a928c1dcb51024ac8d4d4" + integrity sha512-xT27bhYvkjidKCmGt3Dy4tx12Hk4oI9G/6vQFUdDXV1WSk50tysswHe67ckgcSU95yRPcnLVQicVpM3cAH6/AA== dependencies: "@slack/logger" ">=1.0.0 <3.0.0" - "@slack/types" "^1.2.1" + "@slack/types" "^1.7.0" "@types/is-stream" "^1.1.0" "@types/node" ">=8.9.0" - "@types/p-queue" "^2.3.2" - axios "^0.18.0" + axios "^0.19.0" eventemitter3 "^3.1.0" form-data "^2.5.0" is-stream "^1.1.0" - p-queue "^2.4.2" + p-queue "^6.6.1" p-retry "^4.0.0" "@types/aws-lambda@^8.10.19": @@ -165,26 +164,16 @@ dependencies: "@types/node" "*" -"@types/node@*": - version "13.1.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385" - integrity sha512-B8emQA1qeKerqd1dmIsQYnXi+mmAzTB7flExjmy5X1aVAKFNNNDubkavwR13kR6JnpeLp3aLoJhwn9trWPAyFQ== - -"@types/node@>=8.9.0": - version "13.7.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d" - integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA== +"@types/node@*", "@types/node@>=8.9.0": + version "14.14.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.6.tgz#146d3da57b3c636cc0d1769396ce1cfa8991147f" + integrity sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw== "@types/node@^8.10.50": version "8.10.59" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== -"@types/p-queue@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8" - integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ== - "@types/request@^2.48.2": version "2.48.4" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e" @@ -452,6 +441,13 @@ axios@^0.18.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + babel-eslint@^10.0.1: version "10.0.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" @@ -1305,6 +1301,11 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -2428,7 +2429,19 @@ mime-db@1.42.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== -mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.25" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437" integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg== @@ -2824,10 +2837,13 @@ p-map@^2.0.0, p-map@^2.1.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-queue@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" - integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng== +p-queue@^6.6.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" p-retry@^4.0.0: version "4.2.0" @@ -2844,6 +2860,13 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"