const { App: SlackApp } = require('@slack/bolt') const config = require('../config') const temperatureChannelId = 'C034156CE03' const hvackerBotUserId = 'U0344TFA7HQ' const sageUserId = 'U028BMEBWBV' const pollingMinutes = 5 const pollingPeriod = 1000 * 60 * pollingMinutes const colderEmoji = 'snowflake' const hotterEmoji = 'fire' const goodEmoji = '+1' let app try { app = new SlackApp({ token: config.slackBotToken, signingSecret: config.slackSigningSecret, appToken: config.slackAppToken, socketMode: true }) // app.client.conversations.list({types: 'private_channel'}).then(fetched => { // temperatureChannelId = fetched.channels.filter(channel => channel.name === 'thermo-posting')[0].id // console.log('techThermostatChannelId', temperatureChannelId) // }) } catch (e) { console.log('Failed to initialize SlackApp', e) } const pollTriggers = ['!temp', '!temperature', '!imhot', '!imcold'] const halfTriggers = ['change temperature', "i'm cold", "i'm hot", 'quack', 'hvacker', '<@U0344TFA7HQ>'] const sendHelp = async (say, prefix) => { if (prefix) { prefix = prefix + '\n' } else { prefix = '' } await say({ text: prefix + `Sending a message matching any of \`${pollTriggers.join('`, `')}\` will start a temperature poll.\n` + 'At this time I am not capable of actually changing the temperature. Go bug Quade.' }) } const getMessage = async ({ channel, ts }) => app.client.conversations.history({ channel: channel, latest: ts, inclusive: true, limit: 1 }) app.event('reaction_added', async ({ event, context, client, say }) => { for (const listener of reactionListeners) { listener({ event, say }) } }) const ourUsers = { U028BMEBWBV: 'Sage', U02U15RFK4Y: 'Adam', U02AAB54V34: 'Houston', U02KYLVK1GV: 'Quade', U017PG4EL1Y: 'Max', UTDLFGZA5: 'Tyler', U017CB5L1K3: 'Andres' } const activePolls = {} const testId = 'U028BMEBWBV_TEST' let testMode = false app.event('message', async ({ event, context, client, say }) => { console.log(event) if (event.user === sageUserId) { if (event?.text.startsWith('!')) { if (testMode) { await messageSage('Currently in test mode!') } } if (event?.text === '!test') { testMode = !testMode await messageSage(`TestMode: ${testMode} with ID ${testId}`) } else if (event?.text === '!notest') { testMode = false await messageSage(`TestMode: ${testMode}`) } if (testMode) { event.user = testId } // console.log(event.blocks[0].elements[0]) } for (const listener of messageListeners) { listener({ event, say }) } console.log('MSG', ourUsers[event.user], "'" + event.text + "'", new Date().toLocaleTimeString()) if (event.user === 'U028BMEBWBV' && event.channel === 'D0347Q4H9FE') { if (event.text === '!!kill') { process.exit() } if (event.text?.startsWith('!say ') || event.text?.startsWith('!say\n')) { await postToTechThermostatChannel(event.text.substring(4).trim()) return } if (event.text?.startsWith('!saytoq ')) { await messageQuade(event.text.substring(7).trim()) return } if (event.text?.startsWith('!saytos')) { await messageSage(event.text.substring(7).trim()) return } } const eventText = event.text?.toLowerCase() || '' if (eventText.startsWith('!help')) { await sendHelp(say) return } if (!pollTriggers.includes(eventText)) { if (halfTriggers.includes(eventText)) { await sendHelp(say, 'It looks like you might want to change the temperature.') } return } if (activePolls[event.channel]) { await postToTechThermostatChannel({ text: "There's already an active poll in this channel!" }) return } activePolls[event.channel] = true const pollTs = await startPoll() setTimeout(async () => { const reactions = await app.client.reactions.get({ channel: temperatureChannelId, timestamp: pollTs, full: true }) const reactCounts = {} reactions.message.reactions.forEach(reaction => { reactCounts[reaction.name] = reaction.count }) const contentVotes = reactCounts[goodEmoji] const hotterVotes = reactCounts[hotterEmoji] const colderVotes = reactCounts[colderEmoji] let text = 'The people have spoken, and would like to ' if (hotterVotes > colderVotes && hotterVotes > contentVotes) { text += 'raise the temperature, quack.' requestTempChange('Hotter') } else if (colderVotes > hotterVotes && colderVotes > contentVotes) { text += 'lower the temperature, quack quack.' requestTempChange('Colder') } else { text += 'keep the temperature as-is, quaaack.' requestTempChange('Good') } await postToTechThermostatChannel({ text }) delete activePolls[event.channel] }, pollingPeriod) }) ;(async () => { await app.start().catch(console.error) console.log('Slack Bolt has started') // setTimeout(async () => { // await messageSage('') // }, 2000) })() const postToTechThermostatChannel = async optionsOrText => { if (optionsOrText === null || typeof optionsOrText !== 'object') { optionsOrText = { text: optionsOrText } } return app.client.chat.postMessage({ ...optionsOrText, channel: temperatureChannelId }) } const messageSage = async optionsOrText => messageIn(sageUserId, optionsOrText) const messageQuade = async optionsOrText => messageIn('U02KYLVK1GV', optionsOrText) const messageIn = async (channel, optionsOrText) => { if (optionsOrText === null || typeof optionsOrText !== 'object') { optionsOrText = { text: optionsOrText } } return app.client.chat.postMessage({ ...optionsOrText, channel }) } const startPoll = async () => { const sent = await postToTechThermostatChannel({ text: ` Temperature poll requested! In ${pollingMinutes} minutes the temperature will be adjusted.\n` + `Pick :${colderEmoji}: if you want it colder, :${hotterEmoji}: if you want it hotter, or :${goodEmoji}: if you like it how it is.` + `\n(Note that I can't actually change the temperature yet. Make Quade do it!)` }) const addReaction = async emojiName => app.client.reactions.add({ channel: temperatureChannelId, timestamp: sent.ts, name: emojiName }) await addReaction(colderEmoji) await addReaction(hotterEmoji) await addReaction(goodEmoji) return sent.ts } const tempChangeListeners = [] const messageListeners = [] const reactionListeners = [] const requestTempChange = change => { tempChangeListeners.forEach(listener => listener(change)) } const encodeData = (key, data) => `` const decodeData = (key, message) => { const regex = new RegExp(`http://${key}ZZZ[^|]*`) let match = message.match(regex) if (!match) { return match } match = match[0].substring(10 + key.length) // 10 === 'http://'.length + 'ZZZ'.length return JSON.parse(Buffer.from(match, 'base64').toString('utf-8')) } const onReaction = listener => reactionListeners.push(listener) onReaction(async ({ event }) => { if (event.user === sageUserId && event.reaction === 'x') { console.log(event) try { await app.client.chat.delete({channel: event.item.channel, ts: event.item.ts}) } catch (e) { console.error(e) } } }) module.exports = { app, hvackerBotUserId, temperatureChannelId, onAction: app.action, getMessage, updateMessage: app.client.chat.update, postToTechThermostatChannel, onTempChangeRequested: listener => tempChangeListeners.push(listener), onMessage: listener => messageListeners.push(listener), onReaction, encodeData, decodeData, sageUserId, messageSage, messageIn, testMode, testId, ourUsers }