Several tweaks.

Buffer responses on repeated requests (especially useful for '!')
Remove some noisy logging
Add the admin-only !take
Add CANNOT_VOTE to users object
This commit is contained in:
Sage Vaillancourt 2024-11-09 16:59:05 -05:00
parent 2d2e0c9368
commit 35e401cd0b
3 changed files with 141 additions and 55 deletions

View File

@ -140,11 +140,9 @@ const defaultAccess = { hidden: false, condition: alwaysAccessible }
*/ */
const command = (commandNames, helpText, action, { hidden, condition } = defaultAccess) => { const command = (commandNames, helpText, action, { hidden, condition } = defaultAccess) => {
if (!hidden) { if (!hidden) {
console.log(`Initializing command '${commandNames[0]}'`)
commandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')} - ${helpText}\n` commandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')} - ${helpText}\n`
shortCommandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')}` shortCommandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')}`
} else if (condition === adminOnly.condition) { } else if (condition === adminOnly.condition) {
console.log(`Initializing admin command '${commandNames[0]}'`)
} else { } else {
hiddenCommands++ hiddenCommands++
} }
@ -243,7 +241,6 @@ const cardGames = {
help: 'Search for Yu-Gi-Oh cards: !ygo <card name>', help: 'Search for Yu-Gi-Oh cards: !ygo <card name>',
fetch: async name => { fetch: async name => {
const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${name}`; const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${name}`;
console.log('yugioh url', url)
return url return url
}, },
getCardData: ({ data }) => data, getCardData: ({ data }) => data,
@ -293,7 +290,6 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
// return say('Please specify a card name!') // return say('Please specify a card name!')
// } // }
arg = arg.trim() arg = arg.trim()
console.log('arg', arg)
if (cardGame.cards && !cardGame.fetch) { if (cardGame.cards && !cardGame.fetch) {
const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase())) const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase()))
if (fileName) { if (fileName) {
@ -326,7 +322,6 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
const name = cardGame.getCardName(card) const name = cardGame.getCardName(card)
const fileName = gameName + '/' + name const fileName = gameName + '/' + name
if (existsSync(fileName)) { if (existsSync(fileName)) {
console.log(`Using cached file: ${fileName}`)
return postCard(event, name, fileName) return postCard(event, name, fileName)
} }
const file = createWriteStream(fileName) const file = createWriteStream(fileName)
@ -334,7 +329,6 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
response.pipe(file) response.pipe(file)
file.on('finish', async () => { file.on('finish', async () => {
await file.close() await file.close()
console.log(event.channel)
await postCard(event, name, fileName) await postCard(event, name, fileName)
}).on('error', err => { }).on('error', err => {
console.error(err) console.error(err)
@ -448,21 +442,84 @@ const buildHorrorSay = ({ say, event, commandName, c }) => async message => {
} }
const buildSayWithPayload = ({ say, event }) => async msg => { const buildSayWithPayload = ({ say, event }) => async msg => {
const { user, text } = event
const payload = { const payload = {
event: { event: {
text: event.text, text,
user: event.user user
} }
} }
if (typeof(msg) === 'string') { if (typeof(msg) === 'string') {
return say(slack.encodeData('commandPayload', payload) + msg) const userBuff = (messageBuffer[user] ??= { buffer: [], lastSendTs: [] })
const currentTs = Date.now()
const lastSendWasRecent = userBuff.lastSendTs.every(ts => ts > (currentTs - falloffMs))
const doStringSay = currentMsg => {
let sending = ''
const append = text => {
if (sending) {
sending += '\n'
} }
sending += text;
}
if (userBuff.buffer.length) {
let dupCount = 0
let lastMessage = null
const close = () => {
if (dupCount) {
append('\n' + lastMessage + `\n<message duplicated ${dupCount} time${dupCount === 1 ? '' : 's'}>`)
dupCount = 0
} else if (lastMessage !== null) {
append('\n' + lastMessage)
}
}
userBuff.buffer.forEach(buffered => {
if (buffered === lastMessage) {
dupCount++
return
}
close()
lastMessage = buffered
})
close()
}
if (currentMsg) {
append(currentMsg)
}
userBuff.buffer = []
if (userBuff.lastSendTs.length > bucketSize) {
userBuff.lastSendTs.shift()
}
userBuff.lastSendTs.push(currentTs)
if (!sending) {
return
}
return say(slack.encodeData('commandPayload', payload) + sending)
}
if (lastSendWasRecent) {
// Note: Drops buffered payloads
userBuff.buffer.push(msg)
clearTimeout(userBuff.timeoutId)
userBuff.timeoutId = setTimeout(() => doStringSay(null), falloffMs)
return
} else {
return doStringSay(msg)
}
}
return say({ return say({
...msg, ...msg,
text: slack.encodeData('commandPayload', payload) + msg.text text: slack.encodeData('commandPayload', payload) + msg.text
}) })
} }
command(['!ping'], 'Ping', async ({ say }) => say('Hello!'), adminOnly)
const userHasTheGift = user => userHasCheckedQuackgrade(user, 'theGift') const userHasTheGift = user => userHasCheckedQuackgrade(user, 'theGift')
command( command(
@ -490,7 +547,6 @@ const noWinner = 'NO WINNER'
const getPollWinner = async ({ channel, ts }) => { const getPollWinner = async ({ channel, ts }) => {
try { try {
const msg = await slack.getMessage({ channel, ts }) const msg = await slack.getMessage({ channel, ts })
console.log('pollWinner message', JSON.stringify(msg.messages[0]))
let texts = [] let texts = []
let maxVotes = 0 let maxVotes = 0
for (let i = 1; i < msg.messages[0].blocks.length; i++) { for (let i = 1; i < msg.messages[0].blocks.length; i++) {
@ -500,11 +556,8 @@ const getPollWinner = async ({ channel, ts }) => {
continue continue
} }
votes = votes.split('@').length - 1 votes = votes.split('@').length - 1
console.log(`${votes} votes for:`)
text = text.replace(/^\s*:[a-z]*: /, '') text = text.replace(/^\s*:[a-z]*: /, '')
text = text.replace(/\s+`\d+`$/, '') text = text.replace(/\s+`\d+`$/, '')
console.log(`TEXT: '${text}'`)
console.log(``)
if (votes > maxVotes) { if (votes > maxVotes) {
maxVotes = votes maxVotes = votes
texts = [text] texts = [text]
@ -512,7 +565,6 @@ const getPollWinner = async ({ channel, ts }) => {
texts.push(text) texts.push(text)
} }
} }
console.log('TEXTS', texts)
if (texts.length === 1) { if (texts.length === 1) {
return [texts[0], false] return [texts[0], false]
} else if (texts.length > 1) { } else if (texts.length > 1) {
@ -552,13 +604,11 @@ command(
async ({ args, say, user }) => { async ({ args, say, user }) => {
try { try {
const msg = await slack.getMessage({channel: slack.temperatureChannelId, ts: args[0]}) const msg = await slack.getMessage({channel: slack.temperatureChannelId, ts: args[0]})
console.log(JSON.stringify(msg?.messages[0]))
} catch (e) {console.error('!getmsg error', e)} } catch (e) {console.error('!getmsg error', e)}
} }
) )
const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => { const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => {
console.log('messageHandler')
if (event?.subtype === 'bot_message') { if (event?.subtype === 'bot_message') {
return botMessageHandler({ event, say }) return botMessageHandler({ event, say })
} }
@ -566,7 +616,6 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
const words = event?.text?.split(/\s+/) || [] const words = event?.text?.split(/\s+/) || []
const [commandName, ...args] = words const [commandName, ...args] = words
const c = commands.get(commandName) const c = commands.get(commandName)
console.log('getUser')
let user = await getUser(event.user) let user = await getUser(event.user)
if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) { if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) {
return return
@ -633,7 +682,6 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
} }
} }
} }
console.log('getCoins')
Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id)) Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id))
//user.coins = getCoins(event.user) //user.coins = getCoins(event.user)
const isAdmin = event.user?.includes(slack.users.Admin) const isAdmin = event.user?.includes(slack.users.Admin)
@ -824,7 +872,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
blocks: [] blocks: []
}) })
await ack() await ack()
return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`) // return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`)
}) })
slack.onMessage(async msg => { slack.onMessage(async msg => {
@ -1139,7 +1187,7 @@ const doMine = async ({ user, userId, say }) => {
diff = 500 + secondsOfCps(60 * 60, 0.2) diff = 500 + secondsOfCps(60 * 60, 0.2)
prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n` prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n`
addAchievement(user, 'luckyGem', say) addAchievement(user, 'luckyGem', say)
await slack.messageAdmin(`${slack.users[userId]} FOUND A LUCKY GEM COIN WORTH ${commas(diff)} HVAC!`) // await slack.messageAdmin(`${slack.users[userId]} FOUND A LUCKY GEM COIN WORTH ${commas(diff)} HVAC!`)
} else if (random > 0.986) { } else if (random > 0.986) {
diff = 50 + secondsOfCps(60 * 5, 0.1) diff = 50 + secondsOfCps(60 * 5, 0.1)
prefix = `:goldbrick: You found a lucky gold coin worth ${commas(diff)} HVAC!\n` prefix = `:goldbrick: You found a lucky gold coin worth ${commas(diff)} HVAC!\n`
@ -1163,7 +1211,7 @@ command(
'Mine HVAC coins', 'Mine HVAC coins',
async ({ say, user, userId }) => { async ({ say, user, userId }) => {
await say(await doMine({ user, userId, say })) await say(await doMine({ user, userId, say }))
if ((lbIndex++) % 20 == 0) { if ((lbIndex++) % 100 == 0) {
return updateAllLeaderboards() return updateAllLeaderboards()
} }
} }
@ -1257,7 +1305,6 @@ command(
} else { } else {
outcome = 'lost' outcome = 'lost'
} }
console.log(`They ${outcome}`)
//saveGame() //saveGame()
await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`) await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`)
if (outcome === 'lost' && user.lostBetMessage) { if (outcome === 'lost' && user.lostBetMessage) {
@ -1266,7 +1313,7 @@ command(
await trueSay(user.wonBetMessage) await trueSay(user.wonBetMessage)
} }
return updateAllLeaderboards() return updateAllLeaderboards()
} }, dmsOnly
) )
const emojiRegex = /^:[^:\s]*:$/ const emojiRegex = /^:[^:\s]*:$/
@ -1278,7 +1325,6 @@ const validEmoji = async emojiText => {
const validEmojis = (await getEmojis()).emoji const validEmojis = (await getEmojis()).emoji
const noColons = emojiText.replace(/:/g, '') const noColons = emojiText.replace(/:/g, '')
// console.log('validEmojis', validEmojis)
return !!validEmojis[noColons] return !!validEmojis[noColons]
} }
const getEmojis = async () => await slack.app.client.emoji.list() const getEmojis = async () => await slack.app.client.emoji.list()
@ -1368,7 +1414,6 @@ command(
if (!args[0]) { if (!args[0]) {
return say(upgradeText2(user)) return say(upgradeText2(user))
} }
console.log({args: args.join(' ')})
const matcher = fuzzyMatcher(args.join(' ')) const matcher = fuzzyMatcher(args.join(' '))
const u = Object.entries(upgrades).find(([name, upgrade]) => matcher.test(name) || matcher.test(upgrade.name)) const u = Object.entries(upgrades).find(([name, upgrade]) => matcher.test(name) || matcher.test(upgrade.name))
if (!u) { if (!u) {
@ -1418,7 +1463,6 @@ const upgradeBlock = upgradeName => {
const upgradeButton = async ({ body, ack, say, payload }) => { const upgradeButton = async ({ body, ack, say, payload }) => {
await ack() await ack()
const upgrade = payload.action_id.substring(8) const upgrade = payload.action_id.substring(8)
console.log(`upgradeButton ${upgrade} clicked`)
const event = { const event = {
user: body.user.id user: body.user.id
} }
@ -1574,7 +1618,6 @@ command(
'Donate coins to a fellow player\n' + 'Donate coins to a fellow player\n' +
' Send coins by saying \'!gift @player coin_amount\'', ' Send coins by saying \'!gift @player coin_amount\'',
async ({ event, args, say, user, haunted }) => { async ({ event, args, say, user, haunted }) => {
return say(`I'm sorry, but you people can't be trusted anymore.`)
if (haunted) { if (haunted) {
return say(`!give doesn't work while you're haunted.`) return say(`!give doesn't work while you're haunted.`)
} }
@ -1624,13 +1667,55 @@ command(
const last = gifted.pop() const last = gifted.pop()
recipients = gifted.map(t => users[t].name).join(', ') + ', and ' + users[last].name recipients = gifted.map(t => users[t].name).join(', ') + ', and ' + users[last].name
} else { } else {
console.log('gifted', gifted)
console.log('users[gifted[0]]', users[gifted[0]])
recipients = users[gifted[0]].name recipients = users[gifted[0]].name
console.log('recipients', recipients)
} }
await say(`Gifted ${commas(individualAmount)} HVAC to ${recipients}`) await say(`Gifted ${commas(individualAmount)} HVAC to ${recipients}`)
}, adminOnly
)
command(
['!take'],
'Take coins from a player\n' +
' Take coins by saying \'!take @player coin_amount\'',
async ({ event, args, say, user, haunted }) => {
if (haunted) {
return say(`!give doesn't work while you're haunted.`)
} }
let [target, ...amountText] = args
amountText = amountText.join(' ')
const targetId = idFromWord(target)
const targets = [targetId]
if (targetId === event?.user) {
return say(':thonk:')
}
if (!targetId) {
return say('Target must be a valid @')
}
const individualAmount = parseAll(amountText, user.coins, user)
const totalAmount = individualAmount
let victims = []
for (const targetId of targets) {
const targetUser = await getUser(targetId)
targetUser.coins -= individualAmount
victims.push(targetId)
user.coins += individualAmount
}
if (!totalAmount || totalAmount < 0) {
return say('Amount must be a positive integer!')
}
if (user.coins < totalAmount) {
return say(`You don't have that many coins! You have ${commas(user.coins)} HVAC.`)
}
let losers
if (victims.length > 1) {
const last = victims.pop()
losers = victims.map(t => users[t].name).join(', ') + ', and ' + users[last].name
} else {
losers = users[victims[0]].name
}
await say(`Took ${commas(individualAmount)} HVAC from ${losers}`)
}, adminOnly
) )
const getChaosMessage = (user, { channel_type }, prefix = '', postfix = '') => { const getChaosMessage = (user, { channel_type }, prefix = '', postfix = '') => {
@ -1862,7 +1947,6 @@ command(
owner: null owner: null
} }
nfts.push(newNft) nfts.push(newNft)
console.log('addedNft', newNft)
}, adminOnly) }, adminOnly)
command( command(
@ -1938,13 +2022,10 @@ command(
return return
} }
let targetId = idFromWord(target) let targetId = idFromWord(target)
console.log({ user: event.user, target, targetId })
if (event.user === targetId) { if (event.user === targetId) {
return say('What, are you trying to steal from yourself? What, are you stupid?') return say('What, are you trying to steal from yourself? What, are you stupid?')
} }
if (!targetId) {
targetId = slack.users.Admin targetId = slack.users.Admin
}
if (user.coins < amount) { if (user.coins < amount) {
return return
} }
@ -2013,13 +2094,11 @@ command(
args = rest args = rest
} }
console.log({args, channel})
const target = idFromWord(args[0]) const target = idFromWord(args[0])
const [, ...rest] = args const [, ...rest] = args
const userInfo = await slack.app.client.users.info({ const userInfo = await slack.app.client.users.info({
user: target user: target
}) })
console.log(userInfo)
return slack.app.client.chat.postMessage({ return slack.app.client.chat.postMessage({
channel, channel,
text: rest.join(' '), text: rest.join(' '),
@ -2126,12 +2205,8 @@ const updateStonkPrices = () => {
// TODO: Gotta take into account wrapping around to the end of the year // TODO: Gotta take into account wrapping around to the end of the year
Object.entries(stonkMarket.stonks).forEach(([, stonk]) => { Object.entries(stonkMarket.stonks).forEach(([, stonk]) => {
console.log(stonk.pattern)
console.log('try set')
for (let i = stonkMarket.lastDay; i < today; i++) { for (let i = stonkMarket.lastDay; i < today; i++) {
console.log('set lastPrice')
stonk.lastPrice = stonk.price stonk.lastPrice = stonk.price
console.log(stonk.pattern, stonkPatterns)
stonk.price *= 1 + ((stonkPatterns[stonk.pattern] || stonkPatterns.duk)[stonk.index] / 100) stonk.price *= 1 + ((stonkPatterns[stonk.pattern] || stonkPatterns.duk)[stonk.index] / 100)
stonk.index++ stonk.index++
if (stonk.index >= stonkPatterns[stonk.pattern]?.length) { if (stonk.index >= stonkPatterns[stonk.pattern]?.length) {

View File

@ -61,7 +61,13 @@ const parseOr = (parseable, fallback) => {
} }
} }
const makeBackup = () => { let lastBackupTs = 0
const makeBackup = (force) => {
const currentTs = Date.now()
if (lastBackupTs > (currentTs - 60000) && !force) {
return
}
lastBackupTs = currentTs
const fileName = saveDir + 'backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_') const fileName = saveDir + 'backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_')
console.log(`Making backup file: ${fileName}`) console.log(`Making backup file: ${fileName}`)
fs.writeFileSync(fileName, JSON.stringify(game)) fs.writeFileSync(fileName, JSON.stringify(game))
@ -209,6 +215,9 @@ const parseAll = (str, allNum, user) => {
if (str.match(/^\d+$/)) { if (str.match(/^\d+$/)) {
return parseInt(str) return parseInt(str)
} }
if (allNum && str.match(/^some$/)) {
return Math.floor(Math.random() * allNum)
}
if (allNum && str.match(/^\d+%$/)) { if (allNum && str.match(/^\d+%$/)) {
const percent = parseFloat(str) / 100 const percent = parseFloat(str) / 100
if (percent > 1 || percent < 0) { if (percent > 1 || percent < 0) {

View File

@ -122,9 +122,9 @@ app.event('message', async ({ event, context, client, say }) => {
for (const listener of messageListeners) { for (const listener of messageListeners) {
listener({ event, say }) listener({ event, say })
} }
if (event.user) { // if (event.user) {
console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString()) // console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
} // }
if (event.user === users.Admin && event.channel === 'D0347Q4H9FE') { if (event.user === users.Admin && event.channel === 'D0347Q4H9FE') {
if (event.text === '!!kill') { if (event.text === '!!kill') {
saveGame('!!kill', true) saveGame('!!kill', true)
@ -198,7 +198,9 @@ app.event('message', async ({ event, context, client, say }) => {
})) }))
const reactCounts = {} const reactCounts = {}
Object.entries(reactPosters).forEach(([id, votes]) => { Object.entries(reactPosters)
.filter(([id]) => !users.CANNOT_VOTE?.includes(users[id]))
.forEach(([id, votes]) => {
console.log(`VOTES FROM ${id}:`, votes) console.log(`VOTES FROM ${id}:`, votes)
votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].find(emoji => v.startsWith(emoji))) votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].find(emoji => v.startsWith(emoji)))
if (votes.length === 1) { if (votes.length === 1) {