Several new features and bugfixes

Toying with a postgres storage solution (make getUser async to prepare).
Post oneShot images from local files instead of just external links.
!tboi
This commit is contained in:
Sage Vaillancourt 2024-09-19 09:40:27 -04:00
parent 7bc1455e37
commit 2d2e0c9368
21 changed files with 566 additions and 144 deletions

4
.gitignore vendored
View File

@ -1,6 +1,10 @@
script.sh
.idea/
backups/
hvacoins.json
hvackerconfig.json
users.json
standup.json
# Logs
logs
*.log

BIN
images/FKYdeqo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
images/IUX6R26.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
images/QwuCQZA.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
images/VCvfvdz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/XCg7WDz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
images/dBWgFfQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
images/eFreg7Y.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
images/i1YtW7m.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
images/squeedward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

255
package-lock.json generated
View File

@ -14,7 +14,8 @@
"base-64": "^1.0.0",
"express": "^4.17.3",
"fs": "0.0.1-security",
"jest": "27.0.6"
"jest": "27.0.6",
"pg": "^8.11.3"
}
},
"node_modules/@ampproject/remapping": {
@ -1800,6 +1801,14 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
"engines": {
"node": ">=4"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -4619,6 +4628,11 @@
"node": ">=6"
}
},
"node_modules/packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@ -4691,6 +4705,89 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"license": "MIT"
},
"node_modules/pg": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.6.2",
"pg-pool": "^3.6.1",
"pg-protocol": "^1.6.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.1.1"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -4739,6 +4836,41 @@
"semver-compare": "^1.0.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -5172,6 +5304,14 @@
"source-map": "^0.6.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -5764,6 +5904,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"engines": {
"node": ">=0.4"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -7132,6 +7280,11 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -9067,6 +9220,11 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@ -9113,6 +9271,68 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"pg": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-cloudflare": "^1.1.1",
"pg-connection-string": "^2.6.2",
"pg-pool": "^3.6.1",
"pg-protocol": "^1.6.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"pg-connection-string": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
},
"pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
"requires": {}
},
"pg-protocol": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
},
"pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"requires": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"requires": {
"split2": "^4.1.0"
}
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -9144,6 +9364,29 @@
"semver-compare": "^1.0.0"
}
},
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
},
"postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
},
"postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -9427,6 +9670,11 @@
"source-map": "^0.6.0"
}
},
"split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -9816,6 +10064,11 @@
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -21,7 +21,8 @@
"base-64": "^1.0.0",
"express": "^4.17.3",
"fs": "0.0.1-security",
"jest": "27.0.6"
"jest": "27.0.6",
"pg": "^8.11.3"
},
"standard": {
"env": "jest"

View File

@ -1,4 +1,11 @@
#!/bin/bash
export PGHOST=localhost
export PGUSER="postgres"
export PGDATABASE="postgres"
export PGPASSWORD="thisisthesoundofamannamedpostgres"
export PGPORT=5432
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

View File

@ -1,5 +1,5 @@
const buyableItems = require('./buyableItems')
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher, calculateCost } = require('./utils')
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher, calculateCost, saveUser } = require('./utils')
const slack = require('../../slack')
const leaderboardUpdater = {}
@ -153,6 +153,7 @@ const buyRoute = async ({ event, say, args, user }) => {
const countString = quantity === 1 ? 'one' : quantity
await say(`You bought ${countString} :${buyableItem.emoji}:`)
await saveUser(event.user, user, `Buying ${countString} :${buyableItem.emoji}:`)
}
const buyButton = async ({ body, ack, say, payload }) => {
@ -162,7 +163,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
const event = {
user: body.user.id
}
const user = getUser(event.user)
const user = await getUser(event.user)
const words = ['', buying, body.actions[0].text]
const [commandName, ...args] = words
@ -177,6 +178,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
...buyText2(highestCoins, user, extraMessage)
})
await leaderboardUpdater.updateAllLeaderboards()
await saveUser(event.user, user, `buyButton ${buying} clicked`)
}
Object.keys(buyableItems).forEach(itemName => slack.app.action('buy_' + itemName, buyButton))

View File

@ -136,7 +136,7 @@ const addInteraction = ({ actionId, perform }) =>
try {
await ack()
game.pet ??= makePet()
const [everyone, local] = perform(game.pet, getUser(body.user.id))
const [everyone, local] = perform(game.pet, await getUser(body.user.id))
await updateEveryone(everyone)
if (local) {
await say(local)

View File

@ -8,6 +8,8 @@ const {
idFromWord,
getCoins,
getUser,
getUserSync,
saveUser,
commas,
addAchievement,
shufflePercent,
@ -25,13 +27,15 @@ const {
userHasCheckedQuackgrade,
fuzzyMatcher,
addCoins,
game, updateAll
game,
updateAll,
logMemoryUsage
} = require('./utils')
const { nfts, squad, users, horrors, stonkMarket, pet } = game
const pets = require('./gotcha')
const exec = require('child_process').exec
const { createReadStream, createWriteStream, existsSync, readFileSync} = require('fs')
const { createReadStream, createWriteStream, existsSync, readFileSync, writeFileSync } = require('fs')
const { readdir } = require('fs/promises')
const slack = require('../../slack')
@ -80,7 +84,9 @@ const upgradeText = (user, showOwned = false) => {
const hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
const alwaysAccessible = () => true
const alwaysAlwaysAccessible = () => true
const adminOnly = {
hidden: true,
condition: ({ event, say }) => {
@ -91,10 +97,12 @@ const adminOnly = {
return true
}
}
const testOnly = {
hidden: true,
condition: ({ event }) => event.user.includes('TEST')
}
const dmsOnly = {
hidden: false,
condition: async ({ event, say, commandName }) => {
@ -105,6 +113,7 @@ const dmsOnly = {
return true
}
}
const prestigeOnly = {
hidden: false,
condition: async ({ event, say, commandName, user }) => {
@ -181,6 +190,37 @@ const postCard = async (event, name, fileName) =>
file: createReadStream(fileName)
})
const findLineStartingWith = (prefix, text) => {
const lines = text.split(/\s*\n\s*/g)
const line = lines.filter(line => line.startsWith(prefix))[0]
if (!line) {
return
}
return line.substring(prefix.length)
}
const findLinesNotStartingWith = (prefixes, text) => {
const lines = text.split(/\s*\n\s*/g)
const lineStartsWithAnyPrefix = line => prefixes.some(prefix => line.startsWith(prefix))
return lines.filter(line => !lineStartsWithAnyPrefix(line)).join('\n')
}
const isaacData = (() => {
const parsed = JSON.parse(readFileSync('isaac-processed.json'))
parsed.allItems = [...parsed.items, ...parsed.cards, ...parsed.trinkets]
parsed.allItems = Object.fromEntries(parsed.allItems.map(item => [item.name.toLowerCase().trim(), {
...item,
itemId: findLineStartingWith('ItemID: ', item.text),
quality: findLineStartingWith('Quality: ', item.text),
type: findLineStartingWith('Type: ', item.text),
rechargeTime: findLineStartingWith('Recharge Time: ', item.text),
itemPools: findLineStartingWith('Item Pool: ', item.text)?.split(', '),
description: findLinesNotStartingWith(['ItemID', 'Quality: ', 'Type: ', 'Recharge Time: ', 'Item Pool: '], item.text)
}]))
writeFileSync('isaac-new.json', JSON.stringify(parsed.allItems, null, 2))
return parsed
})()
const cardGames = {
digimon: {
names: ['!digimon', '!digi'],
@ -191,7 +231,7 @@ const cardGames = {
getCardImageUrl: card => card.image_url
},
pokemon: {
names: ['!pokemon', '!poke', '!pok', '!yugioh', '!ygo'],
names: ['!pokemon', '!poke', '!pok'],
help: 'Search for Pokemon cards: !pok <card name>',
fetch: async name => `https://api.pokemontcg.io/v2/cards?q=name:${name}`,
getCardData: ({ data }) => data,
@ -199,7 +239,7 @@ const cardGames = {
getCardImageUrl: card => card.images.large
},
yugioh: {
names: [],
names: ['!yugioh', '!ygo'],
help: 'Search for Yu-Gi-Oh cards: !ygo <card name>',
fetch: async name => {
const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${name}`;
@ -210,42 +250,33 @@ const cardGames = {
getCardName: card => card.name,
getCardImageUrl: card => card.card_images[0].image_url
},
// lotrOld: {
// names: ['!lotrOld'],
// help: 'Search for Lord of the Rings cards: !lotrOld <card name>',
// cards: async () => JSON.parse(readFileSync('lotrOld/lotr_cards.json').toString()),
// fetch: async name => ({ json: () => {
// const matcher = fuzzyMatcher(name?.toLowerCase())
// const exact = cardGames.lotrOld.cards.filter(card => card.name?.toLowerCase() === name.toLowerCase())
// if (exact.length) {
// return exact
// }
// return cardGames.lotrOld.cards.filter(card => matcher.test(card.name))
// } }),
// getCardData: data => data,
// getCardName: card => card.name,
// getCardImageUrl: card => 'https://ringsdb.com' + card.imagesrc
// },
// lotr: {
// names: ['!lotr'],
// help: 'Search for Lord of the Rings cards: !lotr <card name>',
// cards: async () => JSON.parse(readFileSync('lotr/cards.json').toString()),
// fetch: async name => ({ json: () => {
// const matcher = fuzzyMatcher(name?.toLowerCase())
// const exact = cardGames.lotr.cards.filter(card => card.name?.toLowerCase() === name.toLowerCase())
// if (exact.length) {
// return [exact[0]]
// }
// return cardGames.lotr.cards.filter(card => matcher.test(card.name)).filter(card => !card.name.includes('('))
// } }),
// getCardData: data => data,
// getCardName: card => card.name,
// getCardImageUrl: card => 'https://lotrtcgwiki.com/wiki/_media/cards:' + card.id + '.jpg'
// },
playingCards: {
names: ['!playing', '!pc'],
help: 'Search for playing cards cards: !pc <card name>',
cards: async () => readdir('playingCards')
},
isaac: {
names: ['!isaac', '!tboi'],
help: 'Search for TBOI items, cards, and trinkets',
cards: async () => Object.keys(isaacData.allItems),
fetch: async (name, say) => {
name = name.toLowerCase()
let matches = [isaacData.allItems[name]].filter(Boolean)
if (!matches.length) {
matches = Object.values(isaacData.allItems).filter(item => item.name.toLowerCase().includes(name))
}
if (!matches.length) {
matches = Object.values(isaacData.allItems).filter(item => item.text.toLowerCase().includes(name))
}
//say('```' + JSON.stringify(matches, null, 2) + '```')
if (!matches.length) {
await say('No matches found!')
return
}
const etcText = matches.length == 1 ? "" : `and ${matches.length - 1} other matches`
const match = matches[0]
await say(match.name + ': ' + match.description.replaceAll('\t', '').replaceAll(/ +/g, '').replaceAll(/\n\*,.*/g, ''))
},
}
}
@ -256,11 +287,13 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
command(
cardGame.names,
cardGame.help,
async ({ args, event, say }) => {
const arg = args?.join(' ')
if (!args?.length || !arg) {
return say('Please specify a card name!')
}
async ({ args, event, say }) => args?.join(' ').split(',').forEach(async arg => {
// const arg = args?.join(' ')
// if (!args?.length || !arg) {
// return say('Please specify a card name!')
// }
arg = arg.trim()
console.log('arg', arg)
if (cardGame.cards && !cardGame.fetch) {
const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase()))
if (fileName) {
@ -268,10 +301,13 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
}
return
}
let response = await cardGame.fetch(arg)
let response = await cardGame.fetch(arg, say)
if (typeof response === 'string') {
response = await fetch(response)
}
if (!response) {
return
}
const json = await response.json()
const data = cardGame.getCardData(json)
if (!data?.length) {
@ -307,7 +343,7 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
} catch (e) {
console.error(e)
}
},
}),
{
hidden: false,
condition: alwaysAlwaysAccessible
@ -437,7 +473,6 @@ command(
return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`)
}
user.isDisabled = true
//saveGame()
return say('.')
}, { hidden: true })
@ -523,6 +558,7 @@ command(
)
const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => {
console.log('messageHandler')
if (event?.subtype === 'bot_message') {
return botMessageHandler({ event, say })
}
@ -530,7 +566,8 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
const words = event?.text?.split(/\s+/) || []
const [commandName, ...args] = words
const c = commands.get(commandName)
let user = getUser(event.user)
console.log('getUser')
let user = await getUser(event.user)
if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) {
return
}
@ -573,7 +610,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
haunted = true
const [disabledId] = getRandomFromArray(disabledUsers)
event.user = disabledId
user = getUser(event.user)
user = await getUser(event.user)
const userInfo = await slack.app.client.users.info({
user: disabledId
})
@ -596,6 +633,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
}
}
}
console.log('getCoins')
Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id))
//user.coins = getCoins(event.user)
const isAdmin = event.user?.includes(slack.users.Admin)
@ -635,6 +673,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
return
}
const before = JSON.stringify(user)
await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted, isAdmin })
if (!isRecycle) {
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
@ -648,8 +687,13 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
setTimeout(() => lightning({ channel: event.channel, say, trueSay, words, user }), 10000)
}
}
const after = JSON.stringify(user)
const endTime = new Date()
saveGame(`command ${event.text} finished in ${endTime - startTime}ms`)
if (before !== after) {
await saveUser(event.user, user, `command ${event.text} finished in ${endTime - startTime}ms`)
} else {
console.error(event.user, user, `command ${event.text} requested a redundant user save!`)
}
}
slack.onReaction(async ({ event }) => {
@ -732,7 +776,7 @@ command(
'Send a lighting strike to the given player.',
async({ args, say, }) => {
const targetId = idFromWord(args[0])
await lightning({ user: getUser(targetId), ms: 15000, channel: targetId})
await lightning({ user: await getUser(targetId), ms: 15000, channel: targetId})
return say(`Sent a bolt of lighting to <@${targetId}>`)
}, adminOnly)
@ -742,14 +786,14 @@ command(
async ({ say, args }) => {
// await dedicatedPlayers.forEach(async player => {
// await lightning({
// user: getUser(player),
// user: await getUser(player),
// ms: 30000,
// channel: player
// })
// })
const targetId = idFromWord(args[0])
for (let i = 0; i < 10; i++) {
setTimeout(async () => await lightning({ user: getUser(targetId), ms: 1600, channel: targetId, multiplier: 0.02}), i * 1500)
setTimeout(async () => await lightning({ user: await getUser(targetId), ms: 1600, channel: targetId, multiplier: 0.02}), i * 1500)
}
return say(`Sent a lighting storm to <@${targetId}>`)
// return say(`Sent a bolt of lighting to the dedicated players`)
@ -761,7 +805,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
return
}
const c = getCoins(body.user.id)
const user = getUser(body.user.id)
const user = await getUser(body.user.id)
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
let payout = secondsOfCps(60 * 30)
if (payout > (0.2 * c)) {
@ -771,7 +815,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts]
addCoins(user, (c + payout) - user.coins)
delete strikes[body.message.ts]
saveGame('user bottled a lightning strike')
saveUser(body.user.id, user, 'bottled a lightning strike')
await slack.app.client.chat.update({
channel: body.channel.id,
@ -795,7 +839,7 @@ command(
['!cleanusers'],
'Calls getUser() on all users, ensuring a valid state.',
async ({ say }) => {
Object.keys(users).forEach(userId => { getUser(userId) })
Object.keys(users).forEach(async userId => { await getUser(userId) })
return say('```Cleaning ' + JSON.stringify(Object.keys(users), null, 2) + '```')
}, adminOnly)
@ -916,7 +960,6 @@ command(
return say(`Your password may not contain spaces!`)
}
user.pwHash = webapi.makeHash(args[0])
//saveGame()
await say(`Password encoded as ${user.pwHash}`)
}
, { hidden: true })
@ -946,7 +989,6 @@ command(
user.coins -= cost
horrors.hvackerHelp += 1
horrors.hvackerLast = dayOfYear()
//saveGame()
await say('I feel a bit better. Thank you...')
}, { hidden: true })
@ -982,7 +1024,7 @@ command(
async ({ say, args }) => {
const achName = args[0]
const target = idFromWord(args[1])
await removeAchievement(getUser(target), achName, say)
await removeAchievement(await getUser(target), achName, say)
}, adminOnly)
command(
@ -1055,7 +1097,7 @@ command(['!cps'],
// }
// } catch (e) {}
//
// const text = await doMine({ user: getUser(event.user), userId: event.user, say })
// const text = await doMine({ user: await getUser(event.user), userId: event.user, say })
// console.log('miningHeat:', miningHeat)
// if (miningHeat < maxTemp) {
// console.log(`${slack.users[event.user]} is pick mining.`);
@ -1112,7 +1154,6 @@ const doMine = async ({ user, userId, say }) => {
prefix = `You mined ${commas(diff)} HVAC.\n`
}
addCoins(user, diff)
//saveGame()
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
}
@ -1142,7 +1183,7 @@ command(
async ({ event, args, trueSay }) => {
const [impersonating, ...newWords] = args
event.user = idFromWord(impersonating)
getUser(event.user)
await getUser(event.user)
const isDisabled = users[event.user].isDisabled
users[event.user].isDisabled = false
event.text = newWords.join(' ')
@ -1154,7 +1195,7 @@ command(
['!enable'],
'Enable the given user',
async ({ args }) => {
const user = getUser(idFromWord(args[0]))
const user = await getUser(idFromWord(args[0]))
if (user.isDisabled) {
user.isDisabled = false
//saveGame()
@ -1167,7 +1208,7 @@ command(
['!disable'],
'Disable the given user',
async ({ args }) => {
const user = getUser(idFromWord(args[0]))
const user = await getUser(idFromWord(args[0]))
user.isDisabled = true
}, adminOnly)
@ -1381,7 +1422,7 @@ const upgradeButton = async ({ body, ack, say, payload }) => {
const event = {
user: body.user.id
}
const user = getUser(event.user, true)
const user = await getUser(event.user, true)
const words = ['!upgrade', upgrade]
const [commandName, ...args] = words
let extraMessage = ''
@ -1423,9 +1464,18 @@ const squadText = () => {
`:${emoji}: *${name}* - ${commas(remaining)} HVAC remaining.\n_${description}_`
return currentUpgradeText(current)
}
return 'No more squadgrades currently available.'
return 'All squadgrades are unlocked!\n\n' + Object.values(squadUpgrades).map(({ name, cost, emoji, description }) =>
`:${emoji}: *${name}* - ${commas(cost)} HVAC.\n_${description}_`).join('\n\n')
}
command(
['!cat'],
'View your total all-time coins collected',
async ({ say, user }) => {
await say(`You've earned a total of ${commas(user.coinsAllTime)} HVAC`)
}
)
command(
['!squad', '!sq'],
'Buy upgrades that help the whole team.\n' +
@ -1509,7 +1559,7 @@ command(
const humanMembers = members.filter(name => name.length === 11)
return say(`Hvacker owns ${humanMembers.length} souls.`)
}
const user = getUser(targetId)
const user = await getUser(targetId)
if (user.isDisabled) {
return say(`<@${targetId}> is no longer with us.`)
}
@ -1524,6 +1574,7 @@ command(
'Donate coins to a fellow player\n' +
' Send coins by saying \'!gift @player coin_amount\'',
async ({ event, args, say, user, haunted }) => {
return say(`I'm sorry, but you people can't be trusted anymore.`)
if (haunted) {
return say(`!give doesn't work while you're haunted.`)
}
@ -1559,7 +1610,7 @@ command(
}
let gifted = []
for (const targetId of targets) {
const targetUser = getUser(targetId)
const targetUser = await getUser(targetId)
user.coins -= individualAmount
if (user.coinsAllTime < 10000) {
// return say('Let \'em play for a bit, ay?')
@ -1696,14 +1747,15 @@ command(
})
const strike = user => user.isDisabled ? '~' : ''
const struck = (user, string) => strike(user) + string + strike(user)
const generateLeaderboard = ({ args }) => {
const generateLeaderboard = ({ args }, showCat) => {
let index = 1
return Object.entries(users)
.filter(([, user]) => (!user.isDisabled || args[0] === 'all') && (Object.entries(user.items).length > 0 || user.prestige))
.filter(([, user]) => (!user.isDisabled || args.includes('all')) && user.name && !['Hvacker', '???', 'TEST-USER'].includes(user.name))
.sort(([id, user1], [id2, user2]) => {
const leftPrestige = getUser(id).prestige
const rightPrestige = getUser(id2).prestige
const leftPrestige = getUserSync(id).prestige
const rightPrestige = getUserSync(id2).prestige
if (leftPrestige > rightPrestige) {
return -1
}
@ -1712,25 +1764,26 @@ const generateLeaderboard = ({ args }) => {
}
return getCPS(user1) > getCPS(user2)
})
.map(([id, u]) => `${strike(u)}${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUser(id)))} CPS - ${commas(getCoins(id))} HVAC${strike(u)}`)
.map(([id, u]) => `${strike(u)}${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUserSync(id)))} CPS - ${commas(getCoins(id))} HVAC${showCat ? ` (${commas(users[id].coinsAllTime)} all-time)` : ''}${strike(u)}`)
.join('\n')
}
command(
['!leaderboard', '!lb'],
'Show the top HVAC-earners, ranked by prestige, then CPS',
async ({ say, user, args }) => {
async ({ say, user, args, event }) => {
// if ((event.user === slack.users.Houston || event.user === slack.users.Admin) && event.channel_type.includes('im')) {
// return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
// }
game.leaderboardChannels ??= {}
await say(generateLeaderboard({ args })).then(({ channel, ts }) => {
addAchievement(user, 'leaderBoardViewer', say)
if (args[0] === 'all') {
const showCat = event.user === slack.users.Admin && args.includes('cat')
await say(generateLeaderboard({ args }, showCat)).then(({ channel, ts }) => {
addAchievement(user, 'leaderBoardViewer', say)
if (args.includes('all') || showCat) {
return
}
game.leaderboardChannels[channel] = ts
return updateAllLeaderboards({ channel, ts })
game.leaderboardChannels[channel] = ts
return updateAllLeaderboards({ channel, ts })
})
}
)
@ -1751,7 +1804,20 @@ const oneShot = (name, helpText, message, achievementName) => {
command(names, helpText, async ({ say, user }) => {
await say(message)
await slack.messageAdmin(`Wow buddy they like your ${name} joke.`)
if (achievementName) {
addAchievement(user, achievementName, say)
}
}, { hidden: true })
}
const oneShotFile = async (name, helpText, text, filePath, achievementName) => {
const names = Array.isArray(name) ? name : [name]
command(names, helpText, async ({ event }) => {
await slack.app.client.files.upload({
channels: event.channel,
initial_comment: text,
file: createReadStream(filePath)
})
if (achievementName) {
addAchievement(user, achievementName, say)
}
@ -1759,15 +1825,16 @@ const oneShot = (name, helpText, message, achievementName) => {
}
oneShot('!peter-griffin-family-guy', 'Good stuff great comedy.', `Are you sure?\nThis will permanently delete all of your progress and remove you from the game.\nSay !!peter-griffin-family-guy to confirm.`)
oneShot('!santa', 'Ho ho ho!', '<https://i.imgur.com/dBWgFfQ.png|I\'m Santa Quacks>')
oneShotFile('!santa', 'Ho ho ho!', '<https://i.imgur.com/dBWgFfQ.png|I\'m Santa Quacks>')
oneShot('!sugma', 'Not very original.', ':hvacker_angery:')
oneShot('!pog', 'One poggers hvacker', '<https://i.imgur.com/XCg7WDz.png|poggers>')
oneShot('!ligma', 'Not very original.', '<https://i.imgur.com/i1YtW7m.png|no>')
oneShot(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', '<https://i.imgur.com/FKYdeqo.jpg|I go XD style>', 'certifiedCoolGuy')
oneShot('!based', 'Sorry, it\'s a little hard to hear you!', '<https://i.imgur.com/IUX6R26.png|What?>')
oneShot('!shrek', 'Is love and is life.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
oneShot('!sugondese', 'I don\'t like you.', '<https://i.imgur.com/VCvfvdz.png|rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>')
oneShotFile('!pog', 'One poggers hvacker', 'poggers', 'images/XCg7WDz.png')
oneShotFile('!ligma', 'Not very original.', 'no', 'images/i1YtW7m.png')
oneShotFile(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', 'I go XD style', 'images/FKYdeqo.jpg', 'certifiedCoolGuy')
oneShotFile('!based', 'Sorry, it\'s a little hard to hear you!', 'What?', 'images/IUX6R26.png')
oneShotFile('!shrek', 'Is love and is life.', 'Donkey!', 'images/QwuCQZA.png')
oneShotFile('!sugondese', 'I don\'t like you.', 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', 'images/VCvfvdz.png')
oneShot('!bofa', 'bofa deez yes yes we get it', ':goorab:')
oneShotFile('!squeedward', 'Who lives in an easter island head, under the sea?', 'Squeedward', 'images/squeedward.png')
// oneShot('!imaginedragons', 'The worst part about any group of white people is that they could be Imagine Dragons and you\'d have no way of knowing.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
command(
@ -1778,9 +1845,9 @@ command(
command(
['!addnft'],
'Arguments 1 and 2 should be on the first line as name (one word!) and integer price.\n' +
' The second line should be the description of the pieces\n' +
' the picture is everything after the second line',
`Arguments 1 and 2 should be on the first line as name (one word!) and integer price.
The second line should be the description of the pieces
the picture is everything after the second line`,
async ({ event }) => {
let [, name, ...price] = event.text.substring(0, event.text.indexOf('\n')).split(' ')
const rest = event.text.substring(event.text.indexOf('\n') + 1)
@ -1852,7 +1919,7 @@ command(
async ({ args, say }) => {
const target = args[0]
const targetId = idFromWord(target)
const targetUser = getUser(targetId)
const targetUser = await getUser(targetId)
await say(
`${target} is currently earning \`${commas(getCPS(targetUser))}\` HVAC Coin per second.\n\n` +
`They have ${commas(getCoins(targetId))} HVAC Coins\n\n` +
@ -1881,7 +1948,7 @@ command(
if (user.coins < amount) {
return
}
const targetUser = getUser(targetId)
const targetUser = await getUser(targetId)
user.coins -= amount
targetUser.coins += amount
await say(`Stealing is wrong. Gave ${commas(amount)} of your HVAC to ${slack.users[targetId]}`)
@ -1920,7 +1987,7 @@ command(
['!giveach'],
'!giveach @player ach_name',
async ({ args, say, }) => {
addAchievement(getUser(idFromWord(args[0])), args[1], say)
addAchievement(await getUser(idFromWord(args[0])), args[1], say)
//saveGame()
}, adminOnly)
@ -2202,7 +2269,7 @@ command(
command(
['!userjson'],
'Fetch the raw JSON data for your user',
async ({ user, trueSay }) => trueSay(JSON.stringify(user)),
async ({ user, trueSay }) => trueSay('```\n' + JSON.stringify(user) + '\n```'),
{ hidden: true }
)
@ -2261,6 +2328,7 @@ command(
)
webapi.launch()
logMemoryUsage()
module.exports = {
command,

View File

@ -1,11 +1,12 @@
const { addAchievement, saveGame, getUser } = require('./utils')
const { addAchievement, saveGame, getUser, saveUser } = require('./utils')
const slack = require('../../slack')
let loreCount = 0
const l = (text, {correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
const l = (text, {file, correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
loreCount += 1
return {
text,
file,
correctReactions,
correctResponse,
incorrectResponse,
@ -37,13 +38,12 @@ const lore = [
l(`The seventh was Small Bob.`),
l(`The eighth was Maurice, who had quite a few words to say about the French, and was eventually elected president.`),
l(`And the ninth...`),
l(`Well, the ninth might actually amount to something.`),
l(`https://i.imgur.com/eFreg7Y.gif\n`),
l(`Well, the ninth might actually amount to something.`, {file: 'https://i.imgur.com/eFreg7Y.gif'})
]
slack.onReaction(async ({ event, say }) => {
try {
const user = getUser(event.user)
const user = await getUser(event.user)
const item = await slack.getMessage({ channel: event.item.channel, ts: event.item.ts })
const message = item.messages[0].text
const loreData = slack.decodeData('lore', message)
@ -59,7 +59,7 @@ slack.onReaction(async ({ event, say }) => {
console.log('lore:', lore[user.lore])
await say(lore[user.lore].correctResponse)
user.lore += 1
saveGame(`updating ${user.name}'s lore counter`)
saveUser(event.user, user, 'updating lore counter')
} catch (e) {console.error('onReaction error', e)}
})
@ -77,7 +77,7 @@ const loreMessage = (user, say) => {
return `Sorry. I'd love to tell you more, but I'm tired. Please check back later.`
}
const loreRoute = async ({ say, args, user, isAdmin }) => {
const loreRoute = async ({ say, args, user, isAdmin, event }) => {
user.lore ??= 0
if (!args[0]) {
const message = loreMessage(user, say)
@ -92,6 +92,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
if (args[0] === 'reset') {
user.lore = 0
//saveGame()
await saveUser(event.user, user, 'lore reset')
return say(`I have reset your place in the story.`)
}
if (isAdmin) {
@ -105,6 +106,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
const jumpTo = parseInt(args[0])
if (!isNaN(jumpTo)) {
user.lore = jumpTo
await saveUser(event.user, user, 'lore jump')
//saveGame()
}
}

View File

@ -1,4 +1,4 @@
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade, getUser } = require('./utils')
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade, getUser, saveUser } = require('./utils')
const { quackStore } = require('./quackstore')
const buyableItems = require('./buyableItems')
const slack = require('../../slack')
@ -77,6 +77,7 @@ const prestigeConfirmRoute = async ({ event, say, user, YEET }) => {
await say(prestigeMenu(user))
await say(`Say !quack _upgrade-name_ to purchase new quackgrades!`)
await saveUser(event.user, user, 'prestiging')
//await say('You prestiged! Check out !quackstore to see what you can buy!')
}
@ -109,7 +110,7 @@ const quackStoreText = user =>
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
const quackStoreRoute = async ({ user, say, args, YEET }) => {
const quackStoreRoute = async ({ user, say, args, YEET, event }) => {
user.quackUpgrades ??= {}
if (!args[0] || !YEET) {
await say(quackStoreText(user))
@ -131,7 +132,9 @@ const quackStoreRoute = async ({ user, say, args, YEET }) => {
if (quackItem.type === 'starter') {
quackItem.effect(user)
}
await say(`You bought ${args[0]}!`)
await saveUser(event.user, user, 'quackgrade purchased')
}
const buyQuackGradeButton = quackgrade => {
@ -237,7 +240,7 @@ const buyQuackGrade = async ({ body, ack, say, trueSay, payload }) => {
const buying = payload.action_id.substring('buy-quackgrade-'.length)
console.log(`buyQuackGrade ${buying} clicked`)
const user = getUser(body.user.id)
const user = await getUser(body.user.id)
// if (!user.isPrestiging) {
// console.log('You must be prestiging!')
// return say(`You must be prestiging to use this menu!`)
@ -262,7 +265,7 @@ Object.keys(quackStore).forEach(itemName => slack.app.action('buy-quackgrade-' +
slack.app.action('complete_prestige', async ({ body, ack, say }) => {
await ack()
const user = getUser(body.user.id)
const user = await getUser(body.user.id)
delete user.isPrestiging
await slack.app.client.chat.delete({
@ -271,6 +274,7 @@ slack.app.action('complete_prestige', async ({ body, ack, say }) => {
})
await say(`Prestige complete!`)
await saveUser(body.user.id, user, 'prestige complete')
})
const prestigeMenuRoute = async ({ say, user }) => {

View File

@ -1,8 +1,11 @@
const { Pool } = require('pg')
const fs = require('fs')
const achievements = require('./achievements')
const buyableItems = require('./buyableItems')
const { quackStore, getChaos } = require('./quackstore')
const dbPool = new Pool()
let jokes
let slackUsers
const setSlackUsers = users => {
@ -64,18 +67,38 @@ const makeBackup = () => {
fs.writeFileSync(fileName, JSON.stringify(game))
}
const saveUser = async (userId, user, after) => {
return
const name = user.name || userId
if (after) {
console.log(`SAVING ${name} after ${after}`)
} else {
console.log(`SAVING ${name}`, user)
}
await dbPool.query(`
INSERT INTO hvacker_user (slack_id, name, data)
VALUES ($1, $2, $3)
ON CONFLICT (slack_id) DO UPDATE
SET data = EXCLUDED.data`
, [userId, user.name, user])
.catch(console.error)
}
let saves = 0
const saveGame = (after, force = true) => {
const saveGame = (after, force = true, skipLog = false) => {
if (saves % 20 === 0) {
makeBackup()
}
saves += 1
if (force || saves % 10 === 0) {
if (after) {
console.log(`SAVING GAME after ${after}`)
} else {
console.log('SAVING GAME')
if (!skipLog) {
if (after) {
console.log(`SAVING GAME after ${after}`)
} else {
console.log('SAVING GAME')
}
}
// Object.entries(game.users).forEach(([userId, user]) => saveUser(userId, user))
fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2))
}
@ -217,7 +240,19 @@ const calculateCost = ({ itemName, user, quantity = 1 }) => {
}
const game = loadGame()
const { users, nfts, squad } = game
let { users, nfts, squad } = game
const getAllUsers = async () => {
const result = await dbPool.query(`
SELECT slack_id, data
FROM hvacker_user
`).catch(console.error)
return Object.fromEntries(result.rows.map(
({ slack_id: slackId, data }) => [slackId, data]))
}
// getAllUsers().then(collection => {
// game.users = (users = collection)
// })
const setHighestCoins = userId => {
const prevMax = users[userId].highestEver || 0
@ -253,7 +288,24 @@ const getIdFromName = name => {
return null;
}
const getUser = (userId, updateCoins = false) => {
const fetchUser = async (userId, updateCoins = false) => {
const result = await dbPool.query(`
SELECT data
FROM hvacker_user
WHERE slack_id = $1`
, [userId])
.catch(console.error)
return result.rows[0]?.data
}
const getUser = async (userId, updateCoins = false) => {
// users[userId] = await fetchUser(userId)
// console.log('USER', users[userId])
//users[userId] = await fetchUser(userId)
return getUserSync(userId, updateCoins)
}
const getUserSync = (userId, updateCoins = false) => {
users[userId] ??= {}
users[userId].coins ??= 0
users[userId].items ??= {}
@ -264,8 +316,9 @@ const getUser = (userId, updateCoins = false) => {
users[userId].startDate ??= new Date()
// users[userId].name ??= slack.users[userId]
if (updateCoins) {
users[userId].coins = getCoins(userId)
users[userId].coins = getCoins(userId, users[userId])
}
saveGame('getUserSync()', true, true)
return users[userId]
}
@ -276,8 +329,8 @@ const addCoins = (user, add) => {
user.coins = Math.floor(user.coins)
}
const getCoins = userId => {
const user = getUser(userId)
const getCoins = (userId, user) => {
user = user || getUserSync(userId)
const currentTime = getSeconds()
const lastCheck = user.lastCheck || currentTime
const secondsPassed = currentTime - lastCheck
@ -577,8 +630,26 @@ const updateAll = async ({ name, text, blocks, add: { channel, ts } = {} }) => {
// // return alreadyHas
}
const logMemoryUsage = name => {
const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
const memoryData = process.memoryUsage();
const formattedData = {
rss: `${formatMemoryUsage(memoryData.rss)} -> Resident Set Size - total memory allocated for the process execution`,
// heapTotal: `${formatMemoryUsage(memoryData.heapTotal)} -> total size of the allocated heap`,
// heapUsed: `${formatMemoryUsage(memoryData.heapUsed)} -> actual memory used during the execution`,
// external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
}
if (name) {
console.log(name, formattedData)
} else {
console.log(formattedData)
}
}
module.exports = {
saveGame,
saveUser,
makeBackup,
logError,
parseOr,
@ -589,6 +660,7 @@ module.exports = {
addAchievement,
getCoins,
getUser,
getUserSync,
singleItemCps,
getCPS,
getItemCps,
@ -617,5 +689,6 @@ module.exports = {
setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update,
setUpgrades,
setSlackUsers,
setJokes: _jokes => jokes = _jokes
setJokes: _jokes => jokes = _jokes,
logMemoryUsage
}

View File

@ -1,10 +1,10 @@
const express = require('express')
const app = express()
const port = 3001
const port = process.env.HVACKER_API_PORT || 3001
const crypto = require('crypto')
const base64 = require('base-64')
const slack = require('../../slack')
const { game: { users }, getUser, fuzzyMatcher } = require('./utils')
const { game: { users }, getUser, fuzzyMatcher, saveUser } = require('./utils')
const apiGetUserId = hash => {
return Object.entries(users)
@ -71,35 +71,21 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
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)
const user = await 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')
res.send(`User with that password does not exist, or user does not have a password.
See '!setpw help' for assistance.\n`)
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)
@ -109,19 +95,19 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
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 })
await saveUser(event.user, user, 'webapi action')
} 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.`)
await say(`Routing error. Make sure you've set up API access with the !setpw command in slack!
Then you can use calls like ${example}
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' &&

View File

@ -1,6 +1,7 @@
const { App: SlackApp } = require('@slack/bolt')
const config = require('../config')
const fs = require('fs')
const http = require('http')
const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr, setSlackUsers } = require('../games/hvacoins/utils')
const temperatureChannelId = 'C034156CE03'
@ -82,6 +83,9 @@ const activePolls = {}
const testId = 'U028BMEBWBV_TEST'
let testMode = false
app.event('message', async ({ event, context, client, say }) => {
if (event.text?.startsWith('!!!')) {
return
}
if (event.subtype !== 'message_changed' && event?.text !== '!') {
console.log('message.event', {
...event,
@ -89,6 +93,16 @@ app.event('message', async ({ event, context, client, say }) => {
})
}
if (event?.user === users.Admin) {
if (event.files?.length) {
const fileName = Math.random().toString()
const file = fs.createWriteStream(fileName)
await app.client.files.upload({
channels: event.channel,
initial_comment: `Here's your file!`,
file: createReadStream(path)
})
return
}
if (event?.text.startsWith('!')) {
if (testMode) {
await messageAdmin('Currently in test mode!')
@ -215,11 +229,19 @@ app.event('message', async ({ event, context, client, say }) => {
let text
if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
if (users.ThermoController === 'someone with hands') {
text = `@SomeoneWithHands, The people have spoken, and would like to `
} else {
text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
}
text += 'raise the temperature, quack.'
requestTempChange('Hotter')
} else if (colderVotes > hotterVotes && colderVotes > contentVotes) {
text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
if (users.ThermoController === 'someone with hands') {
text = `@SomeoneWithHands, The people have spoken, and would like to `
} else {
text = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
}
text += 'lower the temperature, quack quack.'
requestTempChange('Colder')
} else {