const express = require('express') const app = express() const port = 3001 const crypto = require('crypto') const base64 = require('base-64') const slack = require('../../slack') const { game: { users }, getUser, fuzzyMatcher } = require('./utils') const apiGetUserId = hash => { return Object.entries(users) .filter(([id, user]) => user.pwHash === hash) .map(([id, user]) => id)[0] } const makeHash = pw => crypto.createHash('md5') .update(pw) .digest('hex') const emojiMaps = [ ['mouse2', '🐭'], ['male-office-worker', '🧑‍💼'], ['whale', '🐋'], ['train2', '🚂'], ['fire', '🔥'], ['boomerang', '🪃'], ['new_moon_with_face', '🌚'], ['butterfly', '🦋'], ['mirror', '🪞'], ['quade', '🧔‍♂️'], ['hvacker_angery', '🦆'], ['grey_question', '❓'], ['convenience_store', '🏪'], ['office', '🏢'], ['japanese_castle', '🏯'] ] app.get('/alive', (req, res) => { res.send('OK') }) const illegalCommands = ['!', '!c', '!coin', '!mine'] const lastCalls = {} const addCommand = ({ commandNames, helpText, action, condition, hidden }) => { const route = async (req, res) => { let sent const say = async msg => { if (sent) { console.log('attempted double-send of', { msg }) return } const isObj = typeof msg === 'object' if (isObj) { msg = JSON.stringify(msg, null, 2) } for (const entry of emojiMaps) { msg = msg.replaceAll(`:${entry[0]}:`, entry[1]) } msg = msg.replaceAll(/\*([^*]+)\*/g, '$1') .replaceAll(/_([^_]+)_/g, '$1') res.send((typeof msg === 'object' ? JSON.stringify(msg, null, 2) : msg) + '\n') sent = true } try { const keys = [...Object.keys(req.query)] keys.reverse() const words = [commandNames[0], ...keys] console.log({ words }) const [commandName, ...args] = words console.log('INCOMING API CALL:', commandName, words) const encoded = req.header('Authorization').substring(5) const decoded = base64.decode(encoded).substring(1) const hash = makeHash(decoded) console.log({ hash }) const event = { user: apiGetUserId(hash) } const user = getUser(event.user) if (user.name !== 'TEST-USER' && illegalCommands.includes(commandName?.toLowerCase())) { res.send('Command is illegal over the web api!') return } if (!event.user) { res.status(400) res.send( 'User with that password does not exist, or user does not have a password.\n' + 'See \'!setpw help\' for assistance.\n' ) console.log(' bad password') return } // const lastCall = lastCalls[event.user] || 0 // const secondsBetweenCalls = 2 // const currentTime = Math.floor(new Date().getTime() / 1000) // if (lastCall + secondsBetweenCalls > currentTime) { // res.status(400) // res.send(`Must have at least ${secondsBetweenCalls}s between api calls`) // console.log(' rate limited') // return // } // console.log(` went through for ${slack.users[event.user]}`) // lastCalls[event.user] = currentTime if (words[1] === 'help') { await say(commandNames.map(name => `\`${name}\``).join(', ') + ': ' + helpText) if (commandNames.includes('!coin')) { addAchievement(user, 'weAllNeedHelp', say) } return } const haunted = false //await action({ event, say, words, args, commandName }) const canUse = await condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Admin) }) if (!canUse) { await say(`Command '${words[0]}' not found`) return } await action({ event, say, trueSay: say, words, args, commandName, user, userId: event.user, haunted }) } catch (e) { console.error('route error', e) const example = "`curl --location-trusted -u ':your-pw' quacker.sagev.space/lb`" await say(`Routing error. Make sure you've set up API access with the !setpw command in slack!\n` + `Then you can use calls like ${example}\n` + `N.b. --location-trusted is needed because quacker.sagev.space is technically a redirect, and your headers need to be forwarded.`) } } commandNames.forEach(name => name !== '!!help' && app.get('/' + name.replace(/!/gi, ''), async (req, res) => console.log('route', name.replace('/' + /!/gi, '')) || await route(req, res)) ) } module.exports = { addCommand, makeHash, launch: () => app.listen(port, () => { console.log(`Express listening on port ${port}`) }) }