235 lines
7.4 KiB
JavaScript
235 lines
7.4 KiB
JavaScript
|
const { App: SlackApp } = require('@slack/bolt')
|
||
|
const config = require('../config')
|
||
|
|
||
|
const temperatureChannelId = 'C034156CE03'
|
||
|
const hvackerBotUserId = 'U0344TFA7HQ'
|
||
|
const sageUserId = 'U028BMEBWBV'
|
||
|
|
||
|
const pollingMinutes = 10
|
||
|
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'
|
||
|
}
|
||
|
|
||
|
let activePolls = {}
|
||
|
app.event('message', async ({ event, context, client, say }) => {
|
||
|
console.log(event)
|
||
|
for (const listener of messageListeners) {
|
||
|
listener({ event, say })
|
||
|
}
|
||
|
const words = event.text?.split('\s')
|
||
|
if (event.user === 'U028BMEBWBV' && event.channel === 'D0347Q4H9FE') {
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
let 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('<https://i.imgur.com/VCvfvdz.png|...>')
|
||
|
//}, 2000)
|
||
|
})();
|
||
|
|
||
|
const postToTechThermostatChannel = async optionsOrText => {
|
||
|
if (optionsOrText === null || typeof optionsOrText !== 'object') {
|
||
|
optionsOrText = {
|
||
|
text: optionsOrText
|
||
|
}
|
||
|
}
|
||
|
return await 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 await app.client.chat.postMessage({...optionsOrText, channel})
|
||
|
}
|
||
|
|
||
|
const startPoll = async () => {
|
||
|
const sent = await postToTechThermostatChannel({
|
||
|
text: `<!here|here> Temperature poll requested! In ${pollingMinutes} minute(s) 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 =>
|
||
|
await 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) =>
|
||
|
`<http://${key}ZZZ${Buffer.from(JSON.stringify(data), 'utf-8').toString('base64')}| >`
|
||
|
|
||
|
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, say }) => {
|
||
|
if (event.user === sageUserId && event.reaction === 'x') {
|
||
|
console.log(event)
|
||
|
await app.client.chat.delete({ channel: event.item.channel, ts: event.item.ts })
|
||
|
}
|
||
|
})
|
||
|
|
||
|
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,
|
||
|
ourUsers
|
||
|
}
|