Compare commits

...

7 Commits
v1.0.1 ... main

Author SHA1 Message Date
Sage Vaillancourt 161780c45f Store users in the DB.
Properly reading from it is still not hooked up, at the moment.
2024-11-09 18:00:51 -05:00
Sage Vaillancourt 71df29b27b Undo slight over-deletion on work that was in flight. 2024-11-09 17:24:24 -05:00
Sage Vaillancourt 35e401cd0b 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
2024-11-09 16:59:05 -05:00
Sage Vaillancourt 2d2e0c9368 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
2024-09-19 09:43:40 -04:00
Sage Vaillancourt 7bc1455e37 Some cleanup and corrections.
Jokes and news should work again
2023-12-31 20:29:39 -05:00
Sage Vaillancourt 02256dede6 1.0.2 2023-12-29 23:11:27 -05:00
Sage Vaillancourt 79269558d9 Toying with docker 2023-12-29 23:11:00 -05:00
24 changed files with 752 additions and 203 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore

4
.gitignore vendored
View File

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

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM node:20-alpine
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
RUN mkdir -p /hvacker-saves && chown -R node:node /hvacker-saves
RUN mkdir -p /secrets && chown -R node:node /secrets
WORKDIR /home/node/app
COPY package*.json ./
USER node
RUN npm ci
COPY --chown=node:node . .
RUN mv /home/node/app/hvackerconfig.json /secrets
EXPOSE 3001
CMD ["npm", "start"]

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

259
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "hvacker", "name": "hvacker",
"version": "1.0.1", "version": "1.0.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hvacker", "name": "hvacker",
"version": "1.0.1", "version": "1.0.2",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@slack/bolt": "^3.17.0", "@slack/bolt": "^3.17.0",
@ -14,7 +14,8 @@
"base-64": "^1.0.0", "base-64": "^1.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"jest": "27.0.6" "jest": "27.0.6",
"pg": "^8.11.3"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@ -1800,6 +1801,14 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT" "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": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -4619,6 +4628,11 @@
"node": ">=6" "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": { "node_modules/parse-json": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@ -4691,6 +4705,89 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"license": "MIT" "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": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -4739,6 +4836,41 @@
"semver-compare": "^1.0.0" "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": { "node_modules/prelude-ls": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -5172,6 +5304,14 @@
"source-map": "^0.6.0" "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": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -5764,6 +5904,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT" "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": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "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", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" "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": { "bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "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", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" "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": { "parse-json": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "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", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" "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": { "picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -9144,6 +9364,29 @@
"semver-compare": "^1.0.0" "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": { "prelude-ls": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -9427,6 +9670,11 @@
"source-map": "^0.6.0" "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": { "sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "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", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" "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": { "y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "hvacker", "name": "hvacker",
"version": "1.0.1", "version": "1.0.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -21,7 +21,8 @@
"base-64": "^1.0.0", "base-64": "^1.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"jest": "27.0.6" "jest": "27.0.6",
"pg": "^8.11.3"
}, },
"standard": { "standard": {
"env": "jest" "env": "jest"

View File

@ -1,4 +1,11 @@
#!/bin/bash #!/bin/bash
export PGHOST=localhost
export PGUSER="postgres"
export PGDATABASE="postgres"
export PGPASSWORD="thisisthesoundofamannamedpostgres"
export PGPORT=5432
export NVM_DIR="$HOME/.nvm" export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads 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 [ -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 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 slack = require('../../slack')
const leaderboardUpdater = {} const leaderboardUpdater = {}
@ -153,6 +153,7 @@ const buyRoute = async ({ event, say, args, user }) => {
const countString = quantity === 1 ? 'one' : quantity const countString = quantity === 1 ? 'one' : quantity
await say(`You bought ${countString} :${buyableItem.emoji}:`) await say(`You bought ${countString} :${buyableItem.emoji}:`)
await saveUser(event.user, user, `Buying ${countString} :${buyableItem.emoji}:`)
} }
const buyButton = async ({ body, ack, say, payload }) => { const buyButton = async ({ body, ack, say, payload }) => {
@ -162,7 +163,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
const event = { const event = {
user: body.user.id user: body.user.id
} }
const user = getUser(event.user) const user = await getUser(event.user)
const words = ['', buying, body.actions[0].text] const words = ['', buying, body.actions[0].text]
const [commandName, ...args] = words const [commandName, ...args] = words
@ -177,6 +178,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
...buyText2(highestCoins, user, extraMessage) ...buyText2(highestCoins, user, extraMessage)
}) })
await leaderboardUpdater.updateAllLeaderboards() await leaderboardUpdater.updateAllLeaderboards()
await saveUser(event.user, user, `buyButton ${buying} clicked`)
} }
Object.keys(buyableItems).forEach(itemName => slack.app.action('buy_' + itemName, buyButton)) Object.keys(buyableItems).forEach(itemName => slack.app.action('buy_' + itemName, buyButton))

View File

@ -136,7 +136,7 @@ const addInteraction = ({ actionId, perform }) =>
try { try {
await ack() await ack()
game.pet ??= makePet() 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) await updateEveryone(everyone)
if (local) { if (local) {
await say(local) await say(local)

View File

@ -8,6 +8,8 @@ const {
idFromWord, idFromWord,
getCoins, getCoins,
getUser, getUser,
getUserSync,
saveUser,
commas, commas,
addAchievement, addAchievement,
shufflePercent, shufflePercent,
@ -25,13 +27,15 @@ const {
userHasCheckedQuackgrade, userHasCheckedQuackgrade,
fuzzyMatcher, fuzzyMatcher,
addCoins, addCoins,
game, updateAll game,
updateAll,
logMemoryUsage
} = require('./utils') } = require('./utils')
const { nfts, squad, users, horrors, stonkMarket, pet } = game const { nfts, squad, users, horrors, stonkMarket, pet } = game
const pets = require('./gotcha') const pets = require('./gotcha')
const exec = require('child_process').exec 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 { readdir } = require('fs/promises')
const slack = require('../../slack') 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 hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
const alwaysAccessible = () => true const alwaysAccessible = () => true
const alwaysAlwaysAccessible = () => true const alwaysAlwaysAccessible = () => true
const adminOnly = { const adminOnly = {
hidden: true, hidden: true,
condition: ({ event, say }) => { condition: ({ event, say }) => {
@ -91,10 +97,12 @@ const adminOnly = {
return true return true
} }
} }
const testOnly = { const testOnly = {
hidden: true, hidden: true,
condition: ({ event }) => event.user.includes('TEST') condition: ({ event }) => event.user.includes('TEST')
} }
const dmsOnly = { const dmsOnly = {
hidden: false, hidden: false,
condition: async ({ event, say, commandName }) => { condition: async ({ event, say, commandName }) => {
@ -105,6 +113,7 @@ const dmsOnly = {
return true return true
} }
} }
const prestigeOnly = { const prestigeOnly = {
hidden: false, hidden: false,
condition: async ({ event, say, commandName, user }) => { condition: async ({ event, say, commandName, user }) => {
@ -131,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++
} }
@ -181,6 +188,37 @@ const postCard = async (event, name, fileName) =>
file: createReadStream(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 = { const cardGames = {
digimon: { digimon: {
names: ['!digimon', '!digi'], names: ['!digimon', '!digi'],
@ -191,7 +229,7 @@ const cardGames = {
getCardImageUrl: card => card.image_url getCardImageUrl: card => card.image_url
}, },
pokemon: { pokemon: {
names: ['!pokemon', '!poke', '!pok', '!yugioh', '!ygo'], names: ['!pokemon', '!poke', '!pok'],
help: 'Search for Pokemon cards: !pok <card name>', help: 'Search for Pokemon cards: !pok <card name>',
fetch: async name => `https://api.pokemontcg.io/v2/cards?q=name:${name}`, fetch: async name => `https://api.pokemontcg.io/v2/cards?q=name:${name}`,
getCardData: ({ data }) => data, getCardData: ({ data }) => data,
@ -199,53 +237,43 @@ const cardGames = {
getCardImageUrl: card => card.images.large getCardImageUrl: card => card.images.large
}, },
yugioh: { yugioh: {
names: [], names: ['!yugioh', '!ygo'],
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,
getCardName: card => card.name, getCardName: card => card.name,
getCardImageUrl: card => card.card_images[0].image_url 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: { playingCards: {
names: ['!playing', '!pc'], names: ['!playing', '!pc'],
help: 'Search for playing cards cards: !pc <card name>', help: 'Search for playing cards cards: !pc <card name>',
cards: async () => readdir('playingCards') 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 +284,12 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
command( command(
cardGame.names, cardGame.names,
cardGame.help, cardGame.help,
async ({ args, event, say }) => { async ({ args, event, say }) => args?.join(' ').split(',').forEach(async arg => {
const arg = args?.join(' ') // const arg = args?.join(' ')
if (!args?.length || !arg) { // if (!args?.length || !arg) {
return say('Please specify a card name!') // return say('Please specify a card name!')
} // }
arg = arg.trim()
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) {
@ -268,10 +297,13 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
} }
return return
} }
let response = await cardGame.fetch(arg) let response = await cardGame.fetch(arg, say)
if (typeof response === 'string') { if (typeof response === 'string') {
response = await fetch(response) response = await fetch(response)
} }
if (!response) {
return
}
const json = await response.json() const json = await response.json()
const data = cardGame.getCardData(json) const data = cardGame.getCardData(json)
if (!data?.length) { if (!data?.length) {
@ -290,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)
@ -298,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)
@ -307,7 +337,7 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
}, }),
{ {
hidden: false, hidden: false,
condition: alwaysAlwaysAccessible condition: alwaysAlwaysAccessible
@ -338,7 +368,7 @@ command(
command( command(
['!in'], ['!in'],
'!in <channel> <message>', 'Post a message in a specific channel: !in <channel> <message>',
async ({ args, event }) => { async ({ args, event }) => {
const channel = idFromWord(args[0]) const channel = idFromWord(args[0])
const text = event.text.substring(event.text.indexOf('>') + 1) const text = event.text.substring(event.text.indexOf('>') + 1)
@ -411,22 +441,90 @@ const buildHorrorSay = ({ say, event, commandName, c }) => async message => {
} }
} }
const falloffMs = 5000
const bucketSize = 3
const messageBuffer = {}
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(
@ -437,7 +535,6 @@ command(
return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`) return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`)
} }
user.isDisabled = true user.isDisabled = true
//saveGame()
return say('.') return say('.')
}, { hidden: true }) }, { hidden: true })
@ -455,7 +552,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++) {
@ -465,11 +561,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]
@ -477,7 +570,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) {
@ -517,7 +609,6 @@ 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)}
} }
) )
@ -530,7 +621,7 @@ 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)
let user = getUser(event.user) let user = await getUser(event.user)
if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) { if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) {
return return
} }
@ -573,7 +664,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
haunted = true haunted = true
const [disabledId] = getRandomFromArray(disabledUsers) const [disabledId] = getRandomFromArray(disabledUsers)
event.user = disabledId event.user = disabledId
user = getUser(event.user) user = await getUser(event.user)
const userInfo = await slack.app.client.users.info({ const userInfo = await slack.app.client.users.info({
user: disabledId user: disabledId
}) })
@ -635,6 +726,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
return return
} }
const before = JSON.stringify(user)
await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted, isAdmin }) await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted, isAdmin })
if (!isRecycle) { if (!isRecycle) {
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name]) const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
@ -648,8 +740,13 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
setTimeout(() => lightning({ channel: event.channel, say, trueSay, words, user }), 10000) setTimeout(() => lightning({ channel: event.channel, say, trueSay, words, user }), 10000)
} }
} }
const after = JSON.stringify(user)
const endTime = new Date() 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 }) => { slack.onReaction(async ({ event }) => {
@ -732,7 +829,7 @@ command(
'Send a lighting strike to the given player.', 'Send a lighting strike to the given player.',
async({ args, say, }) => { async({ args, say, }) => {
const targetId = idFromWord(args[0]) 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}>`) return say(`Sent a bolt of lighting to <@${targetId}>`)
}, adminOnly) }, adminOnly)
@ -742,14 +839,14 @@ command(
async ({ say, args }) => { async ({ say, args }) => {
// await dedicatedPlayers.forEach(async player => { // await dedicatedPlayers.forEach(async player => {
// await lightning({ // await lightning({
// user: getUser(player), // user: await getUser(player),
// ms: 30000, // ms: 30000,
// channel: player // channel: player
// }) // })
// }) // })
const targetId = idFromWord(args[0]) const targetId = idFromWord(args[0])
for (let i = 0; i < 10; i++) { 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 lighting storm to <@${targetId}>`)
// return say(`Sent a bolt of lighting to the dedicated players`) // return say(`Sent a bolt of lighting to the dedicated players`)
@ -761,7 +858,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
return return
} }
const c = getCoins(body.user.id) 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) const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
let payout = secondsOfCps(60 * 30) let payout = secondsOfCps(60 * 30)
if (payout > (0.2 * c)) { if (payout > (0.2 * c)) {
@ -771,7 +868,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts] payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts]
addCoins(user, (c + payout) - user.coins) addCoins(user, (c + payout) - user.coins)
delete strikes[body.message.ts] 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({ await slack.app.client.chat.update({
channel: body.channel.id, channel: body.channel.id,
@ -780,7 +877,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 => {
@ -795,7 +892,7 @@ command(
['!cleanusers'], ['!cleanusers'],
'Calls getUser() on all users, ensuring a valid state.', 'Calls getUser() on all users, ensuring a valid state.',
async ({ say }) => { 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) + '```') return say('```Cleaning ' + JSON.stringify(Object.keys(users), null, 2) + '```')
}, adminOnly) }, adminOnly)
@ -916,7 +1013,6 @@ command(
return say(`Your password may not contain spaces!`) return say(`Your password may not contain spaces!`)
} }
user.pwHash = webapi.makeHash(args[0]) user.pwHash = webapi.makeHash(args[0])
//saveGame()
await say(`Password encoded as ${user.pwHash}`) await say(`Password encoded as ${user.pwHash}`)
} }
, { hidden: true }) , { hidden: true })
@ -946,7 +1042,6 @@ command(
user.coins -= cost user.coins -= cost
horrors.hvackerHelp += 1 horrors.hvackerHelp += 1
horrors.hvackerLast = dayOfYear() horrors.hvackerLast = dayOfYear()
//saveGame()
await say('I feel a bit better. Thank you...') await say('I feel a bit better. Thank you...')
}, { hidden: true }) }, { hidden: true })
@ -982,7 +1077,7 @@ command(
async ({ say, args }) => { async ({ say, args }) => {
const achName = args[0] const achName = args[0]
const target = idFromWord(args[1]) const target = idFromWord(args[1])
await removeAchievement(getUser(target), achName, say) await removeAchievement(await getUser(target), achName, say)
}, adminOnly) }, adminOnly)
command( command(
@ -1055,7 +1150,7 @@ command(['!cps'],
// } // }
// } catch (e) {} // } 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) // console.log('miningHeat:', miningHeat)
// if (miningHeat < maxTemp) { // if (miningHeat < maxTemp) {
// console.log(`${slack.users[event.user]} is pick mining.`); // console.log(`${slack.users[event.user]} is pick mining.`);
@ -1097,7 +1192,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`
@ -1112,7 +1207,6 @@ const doMine = async ({ user, userId, say }) => {
prefix = `You mined ${commas(diff)} HVAC.\n` prefix = `You mined ${commas(diff)} HVAC.\n`
} }
addCoins(user, diff) addCoins(user, diff)
//saveGame()
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.` return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
} }
@ -1122,7 +1216,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()
} }
} }
@ -1142,7 +1236,7 @@ command(
async ({ event, args, trueSay }) => { async ({ event, args, trueSay }) => {
const [impersonating, ...newWords] = args const [impersonating, ...newWords] = args
event.user = idFromWord(impersonating) event.user = idFromWord(impersonating)
getUser(event.user) await getUser(event.user)
const isDisabled = users[event.user].isDisabled const isDisabled = users[event.user].isDisabled
users[event.user].isDisabled = false users[event.user].isDisabled = false
event.text = newWords.join(' ') event.text = newWords.join(' ')
@ -1154,7 +1248,7 @@ command(
['!enable'], ['!enable'],
'Enable the given user', 'Enable the given user',
async ({ args }) => { async ({ args }) => {
const user = getUser(idFromWord(args[0])) const user = await getUser(idFromWord(args[0]))
if (user.isDisabled) { if (user.isDisabled) {
user.isDisabled = false user.isDisabled = false
//saveGame() //saveGame()
@ -1167,7 +1261,7 @@ command(
['!disable'], ['!disable'],
'Disable the given user', 'Disable the given user',
async ({ args }) => { async ({ args }) => {
const user = getUser(idFromWord(args[0])) const user = await getUser(idFromWord(args[0]))
user.isDisabled = true user.isDisabled = true
}, adminOnly) }, adminOnly)
@ -1216,7 +1310,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) {
@ -1225,7 +1318,7 @@ command(
await trueSay(user.wonBetMessage) await trueSay(user.wonBetMessage)
} }
return updateAllLeaderboards() return updateAllLeaderboards()
} }, dmsOnly
) )
const emojiRegex = /^:[^:\s]*:$/ const emojiRegex = /^:[^:\s]*:$/
@ -1237,7 +1330,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()
@ -1327,7 +1419,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) {
@ -1377,11 +1468,10 @@ 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
} }
const user = getUser(event.user, true) const user = await getUser(event.user, true)
const words = ['!upgrade', upgrade] const words = ['!upgrade', upgrade]
const [commandName, ...args] = words const [commandName, ...args] = words
let extraMessage = '' let extraMessage = ''
@ -1423,9 +1513,18 @@ const squadText = () => {
`:${emoji}: *${name}* - ${commas(remaining)} HVAC remaining.\n_${description}_` `:${emoji}: *${name}* - ${commas(remaining)} HVAC remaining.\n_${description}_`
return currentUpgradeText(current) 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( command(
['!squad', '!sq'], ['!squad', '!sq'],
'Buy upgrades that help the whole team.\n' + 'Buy upgrades that help the whole team.\n' +
@ -1509,7 +1608,7 @@ command(
const humanMembers = members.filter(name => name.length === 11) const humanMembers = members.filter(name => name.length === 11)
return say(`Hvacker owns ${humanMembers.length} souls.`) return say(`Hvacker owns ${humanMembers.length} souls.`)
} }
const user = getUser(targetId) const user = await getUser(targetId)
if (user.isDisabled) { if (user.isDisabled) {
return say(`<@${targetId}> is no longer with us.`) return say(`<@${targetId}> is no longer with us.`)
} }
@ -1559,7 +1658,7 @@ command(
} }
let gifted = [] let gifted = []
for (const targetId of targets) { for (const targetId of targets) {
const targetUser = getUser(targetId) const targetUser = await getUser(targetId)
user.coins -= individualAmount user.coins -= individualAmount
if (user.coinsAllTime < 10000) { if (user.coinsAllTime < 10000) {
// return say('Let \'em play for a bit, ay?') // return say('Let \'em play for a bit, ay?')
@ -1573,13 +1672,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 = '') => {
@ -1696,14 +1837,15 @@ command(
}) })
const strike = user => user.isDisabled ? '~' : '' const strike = user => user.isDisabled ? '~' : ''
const struck = (user, string) => strike(user) + string + strike(user)
const generateLeaderboard = ({ args }) => { const generateLeaderboard = ({ args }, showCat) => {
let index = 1 let index = 1
return Object.entries(users) 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]) => { .sort(([id, user1], [id2, user2]) => {
const leftPrestige = getUser(id).prestige const leftPrestige = getUserSync(id).prestige
const rightPrestige = getUser(id2).prestige const rightPrestige = getUserSync(id2).prestige
if (leftPrestige > rightPrestige) { if (leftPrestige > rightPrestige) {
return -1 return -1
} }
@ -1712,21 +1854,22 @@ const generateLeaderboard = ({ args }) => {
} }
return getCPS(user1) > getCPS(user2) 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') .join('\n')
} }
command( command(
['!leaderboard', '!lb'], ['!leaderboard', '!lb'],
'Show the top HVAC-earners, ranked by prestige, then CPS', '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')) { // 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` + '```') // return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
// } // }
game.leaderboardChannels ??= {} game.leaderboardChannels ??= {}
await say(generateLeaderboard({ args })).then(({ channel, ts }) => { const showCat = event.user === slack.users.Admin && args.includes('cat')
await say(generateLeaderboard({ args }, showCat)).then(({ channel, ts }) => {
addAchievement(user, 'leaderBoardViewer', say) addAchievement(user, 'leaderBoardViewer', say)
if (args[0] === 'all') { if (args.includes('all') || showCat) {
return return
} }
game.leaderboardChannels[channel] = ts game.leaderboardChannels[channel] = ts
@ -1751,7 +1894,20 @@ const oneShot = (name, helpText, message, achievementName) => {
command(names, helpText, async ({ say, user }) => { command(names, helpText, async ({ say, user }) => {
await say(message) 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) { if (achievementName) {
addAchievement(user, achievementName, say) addAchievement(user, achievementName, say)
} }
@ -1759,15 +1915,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('!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('!sugma', 'Not very original.', ':hvacker_angery:')
oneShot('!pog', 'One poggers hvacker', '<https://i.imgur.com/XCg7WDz.png|poggers>') oneShotFile('!pog', 'One poggers hvacker', 'poggers', 'images/XCg7WDz.png')
oneShot('!ligma', 'Not very original.', '<https://i.imgur.com/i1YtW7m.png|no>') oneShotFile('!ligma', 'Not very original.', 'no', 'images/i1YtW7m.png')
oneShot(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', '<https://i.imgur.com/FKYdeqo.jpg|I go XD style>', 'certifiedCoolGuy') oneShotFile(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', 'I go XD style', 'images/FKYdeqo.jpg', 'certifiedCoolGuy')
oneShot('!based', 'Sorry, it\'s a little hard to hear you!', '<https://i.imgur.com/IUX6R26.png|What?>') oneShotFile('!based', 'Sorry, it\'s a little hard to hear you!', 'What?', 'images/IUX6R26.png')
oneShot('!shrek', 'Is love and is life.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>') oneShotFile('!shrek', 'Is love and is life.', 'Donkey!', 'images/QwuCQZA.png')
oneShot('!sugondese', 'I don\'t like you.', '<https://i.imgur.com/VCvfvdz.png|rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>') oneShotFile('!sugondese', 'I don\'t like you.', 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', 'images/VCvfvdz.png')
oneShot('!bofa', 'bofa deez yes yes we get it', ':goorab:') 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!>') // 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( command(
@ -1778,9 +1935,9 @@ command(
command( command(
['!addnft'], ['!addnft'],
'Arguments 1 and 2 should be on the first line as name (one word!) and integer price.\n' + `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\n' + The second line should be the description of the pieces
' the picture is everything after the second line', the picture is everything after the second line`,
async ({ event }) => { async ({ event }) => {
let [, name, ...price] = event.text.substring(0, event.text.indexOf('\n')).split(' ') let [, name, ...price] = event.text.substring(0, event.text.indexOf('\n')).split(' ')
const rest = event.text.substring(event.text.indexOf('\n') + 1) const rest = event.text.substring(event.text.indexOf('\n') + 1)
@ -1795,7 +1952,6 @@ command(
owner: null owner: null
} }
nfts.push(newNft) nfts.push(newNft)
console.log('addedNft', newNft)
}, adminOnly) }, adminOnly)
command( command(
@ -1852,7 +2008,7 @@ command(
async ({ args, say }) => { async ({ args, say }) => {
const target = args[0] const target = args[0]
const targetId = idFromWord(target) const targetId = idFromWord(target)
const targetUser = getUser(targetId) const targetUser = await getUser(targetId)
await say( await say(
`${target} is currently earning \`${commas(getCPS(targetUser))}\` HVAC Coin per second.\n\n` + `${target} is currently earning \`${commas(getCPS(targetUser))}\` HVAC Coin per second.\n\n` +
`They have ${commas(getCoins(targetId))} HVAC Coins\n\n` + `They have ${commas(getCoins(targetId))} HVAC Coins\n\n` +
@ -1871,17 +2027,14 @@ 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
} }
const targetUser = getUser(targetId) const targetUser = await getUser(targetId)
user.coins -= amount user.coins -= amount
targetUser.coins += amount targetUser.coins += amount
await say(`Stealing is wrong. Gave ${commas(amount)} of your HVAC to ${slack.users[targetId]}`) await say(`Stealing is wrong. Gave ${commas(amount)} of your HVAC to ${slack.users[targetId]}`)
@ -1920,7 +2073,7 @@ command(
['!giveach'], ['!giveach'],
'!giveach @player ach_name', '!giveach @player ach_name',
async ({ args, say, }) => { async ({ args, say, }) => {
addAchievement(getUser(idFromWord(args[0])), args[1], say) addAchievement(await getUser(idFromWord(args[0])), args[1], say)
//saveGame() //saveGame()
}, adminOnly) }, adminOnly)
@ -1946,13 +2099,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(' '),
@ -2059,12 +2210,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) {
@ -2202,7 +2349,7 @@ command(
command( command(
['!userjson'], ['!userjson'],
'Fetch the raw JSON data for your user', '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 } { hidden: true }
) )
@ -2261,6 +2408,7 @@ command(
) )
webapi.launch() webapi.launch()
logMemoryUsage()
module.exports = { module.exports = {
command, command,

View File

@ -1,11 +1,12 @@
const { addAchievement, saveGame, getUser } = require('./utils') const { addAchievement, saveGame, getUser, saveUser } = require('./utils')
const slack = require('../../slack') const slack = require('../../slack')
let loreCount = 0 let loreCount = 0
const l = (text, {correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => { const l = (text, {file, correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
loreCount += 1 loreCount += 1
return { return {
text, text,
file,
correctReactions, correctReactions,
correctResponse, correctResponse,
incorrectResponse, incorrectResponse,
@ -37,13 +38,12 @@ const lore = [
l(`The seventh was Small Bob.`), 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(`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(`And the ninth...`),
l(`Well, the ninth might actually amount to something.`), l(`Well, the ninth might actually amount to something.`, {file: 'https://i.imgur.com/eFreg7Y.gif'})
l(`https://i.imgur.com/eFreg7Y.gif\n`),
] ]
slack.onReaction(async ({ event, say }) => { slack.onReaction(async ({ event, say }) => {
try { 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 item = await slack.getMessage({ channel: event.item.channel, ts: event.item.ts })
const message = item.messages[0].text const message = item.messages[0].text
const loreData = slack.decodeData('lore', message) const loreData = slack.decodeData('lore', message)
@ -59,7 +59,7 @@ slack.onReaction(async ({ event, say }) => {
console.log('lore:', lore[user.lore]) console.log('lore:', lore[user.lore])
await say(lore[user.lore].correctResponse) await say(lore[user.lore].correctResponse)
user.lore += 1 user.lore += 1
saveGame(`updating ${user.name}'s lore counter`) saveUser(event.user, user, 'updating lore counter')
} catch (e) {console.error('onReaction error', e)} } 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.` 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 user.lore ??= 0
if (!args[0]) { if (!args[0]) {
const message = loreMessage(user, say) const message = loreMessage(user, say)
@ -92,6 +92,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
if (args[0] === 'reset') { if (args[0] === 'reset') {
user.lore = 0 user.lore = 0
//saveGame() //saveGame()
await saveUser(event.user, user, 'lore reset')
return say(`I have reset your place in the story.`) return say(`I have reset your place in the story.`)
} }
if (isAdmin) { if (isAdmin) {
@ -105,6 +106,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
const jumpTo = parseInt(args[0]) const jumpTo = parseInt(args[0])
if (!isNaN(jumpTo)) { if (!isNaN(jumpTo)) {
user.lore = jumpTo user.lore = jumpTo
await saveUser(event.user, user, 'lore jump')
//saveGame() //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 { quackStore } = require('./quackstore')
const buyableItems = require('./buyableItems') const buyableItems = require('./buyableItems')
const slack = require('../../slack') const slack = require('../../slack')
@ -77,6 +77,7 @@ const prestigeConfirmRoute = async ({ event, say, user, YEET }) => {
await say(prestigeMenu(user)) await say(prestigeMenu(user))
await say(`Say !quack _upgrade-name_ to purchase new quackgrades!`) 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!') //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.` + `\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%` `\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 ??= {} user.quackUpgrades ??= {}
if (!args[0] || !YEET) { if (!args[0] || !YEET) {
await say(quackStoreText(user)) await say(quackStoreText(user))
@ -131,7 +132,9 @@ const quackStoreRoute = async ({ user, say, args, YEET }) => {
if (quackItem.type === 'starter') { if (quackItem.type === 'starter') {
quackItem.effect(user) quackItem.effect(user)
} }
await say(`You bought ${args[0]}!`) await say(`You bought ${args[0]}!`)
await saveUser(event.user, user, 'quackgrade purchased')
} }
const buyQuackGradeButton = quackgrade => { const buyQuackGradeButton = quackgrade => {
@ -237,7 +240,7 @@ const buyQuackGrade = async ({ body, ack, say, trueSay, payload }) => {
const buying = payload.action_id.substring('buy-quackgrade-'.length) const buying = payload.action_id.substring('buy-quackgrade-'.length)
console.log(`buyQuackGrade ${buying} clicked`) console.log(`buyQuackGrade ${buying} clicked`)
const user = getUser(body.user.id) const user = await getUser(body.user.id)
// if (!user.isPrestiging) { // if (!user.isPrestiging) {
// console.log('You must be prestiging!') // console.log('You must be prestiging!')
// return say(`You must be prestiging to use this menu!`) // 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 }) => { slack.app.action('complete_prestige', async ({ body, ack, say }) => {
await ack() await ack()
const user = getUser(body.user.id) const user = await getUser(body.user.id)
delete user.isPrestiging delete user.isPrestiging
await slack.app.client.chat.delete({ await slack.app.client.chat.delete({
@ -271,6 +274,7 @@ slack.app.action('complete_prestige', async ({ body, ack, say }) => {
}) })
await say(`Prestige complete!`) await say(`Prestige complete!`)
await saveUser(body.user.id, user, 'prestige complete')
}) })
const prestigeMenuRoute = async ({ say, user }) => { const prestigeMenuRoute = async ({ say, user }) => {

View File

@ -1,10 +1,13 @@
const { Pool } = require('pg')
const fs = require('fs') const fs = require('fs')
//const jokes = require('../jokes') const config = require('../../config')
const achievements = require('./achievements') const achievements = require('./achievements')
const buyableItems = require('./buyableItems') const buyableItems = require('./buyableItems')
const { quackStore, getChaos } = require('./quackstore') const { quackStore, getChaos } = require('./quackstore')
const slack = require("../../slack");
const dbPool = new Pool(config.postgres)
let jokes
let slackUsers let slackUsers
const setSlackUsers = users => { const setSlackUsers = users => {
slackUsers = users slackUsers = users
@ -59,24 +62,58 @@ const parseOr = (parseable, fallback) => {
} }
} }
const makeBackup = () => { let lastBackupTs = 0
const fileName = saveDir + 'backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_') const makeBackup = force => {
const currentTs = Date.now()
if (lastBackupTs > (currentTs - 60000) && !force) {
return
}
lastBackupTs = currentTs
const cleanNowString = new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_')
const fileName = `${saveDir}backups/${cleanNowString}-${saveFile}`
console.log(`Making backup file: ${fileName}`) console.log(`Making backup file: ${fileName}`)
fs.writeFileSync(fileName, JSON.stringify(game)) fs.writeFileSync(fileName, JSON.stringify(game))
} }
const saveUser = async (userId, user, after) => {
const name = user.name || userId
if (after) {
console.log(`SAVING ${name} after ${after}`)
} else {
console.log(`SAVING ${name}`, user)
}
return 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)
}
const saveAllUsers = () => Promise.all(
Object.entries(game.users).map(async ([userId, user]) =>
await saveUser(userId, user)
)
).then(() => {
console.log('All users updated in the DB')
})
let saves = 0 let saves = 0
const saveGame = (after, force = true) => { const saveGame = (after, force = true, skipLog = false) => {
if (saves % 20 === 0) { if (saves % 20 === 0) {
makeBackup() makeBackup()
saveAllUsers().catch(console.error)
} }
saves += 1 saves += 1
if (force || saves % 10 === 0) { if (force || saves % 10 === 0) {
if (!skipLog) {
if (after) { if (after) {
console.log(`SAVING GAME after ${after}`) console.log(`SAVING GAME after ${after}`)
} else { } else {
console.log('SAVING GAME') console.log('SAVING GAME')
} }
}
fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2)) fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2))
} }
@ -187,6 +224,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) {
@ -218,7 +258,19 @@ const calculateCost = ({ itemName, user, quantity = 1 }) => {
} }
const game = loadGame() 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 setHighestCoins = userId => {
const prevMax = users[userId].highestEver || 0 const prevMax = users[userId].highestEver || 0
@ -254,7 +306,24 @@ const getIdFromName = name => {
return null; 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] ??= {}
users[userId].coins ??= 0 users[userId].coins ??= 0
users[userId].items ??= {} users[userId].items ??= {}
@ -265,8 +334,9 @@ const getUser = (userId, updateCoins = false) => {
users[userId].startDate ??= new Date() users[userId].startDate ??= new Date()
// users[userId].name ??= slack.users[userId] // users[userId].name ??= slack.users[userId]
if (updateCoins) { if (updateCoins) {
users[userId].coins = getCoins(userId) users[userId].coins = getCoins(userId, users[userId])
} }
saveGame('getUserSync()', true, true)
return users[userId] return users[userId]
} }
@ -277,8 +347,8 @@ const addCoins = (user, add) => {
user.coins = Math.floor(user.coins) user.coins = Math.floor(user.coins)
} }
const getCoins = userId => { const getCoins = (userId, user) => {
const user = getUser(userId) user = user || getUserSync(userId)
const currentTime = getSeconds() const currentTime = getSeconds()
const lastCheck = user.lastCheck || currentTime const lastCheck = user.lastCheck || currentTime
const secondsPassed = currentTime - lastCheck const secondsPassed = currentTime - lastCheck
@ -424,7 +494,7 @@ const shufflePercent = (str, percentOdds) => {
} }
const definitelyShuffle = (str, percentOdds) => { const definitelyShuffle = (str, percentOdds) => {
if (!str) { if (!str || str.length === 1) {
return str return str
} }
if (!percentOdds) { if (!percentOdds) {
@ -578,8 +648,26 @@ const updateAll = async ({ name, text, blocks, add: { channel, ts } = {} }) => {
// // return alreadyHas // // 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 = { module.exports = {
saveGame, saveGame,
saveUser,
makeBackup, makeBackup,
logError, logError,
parseOr, parseOr,
@ -590,6 +678,7 @@ module.exports = {
addAchievement, addAchievement,
getCoins, getCoins,
getUser, getUser,
getUserSync,
singleItemCps, singleItemCps,
getCPS, getCPS,
getItemCps, getItemCps,
@ -617,5 +706,7 @@ module.exports = {
updateAll, updateAll,
setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update, setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update,
setUpgrades, setUpgrades,
setSlackUsers setSlackUsers,
setJokes: _jokes => jokes = _jokes,
logMemoryUsage
} }

View File

@ -1,10 +1,10 @@
const express = require('express') const express = require('express')
const app = express() const app = express()
const port = 3001 const port = process.env.HVACKER_API_PORT || 3001
const crypto = require('crypto') const crypto = require('crypto')
const base64 = require('base-64') const base64 = require('base-64')
const slack = require('../../slack') const slack = require('../../slack')
const { game: { users }, getUser, fuzzyMatcher } = require('./utils') const { game: { users }, getUser, fuzzyMatcher, saveUser } = require('./utils')
const apiGetUserId = hash => { const apiGetUserId = hash => {
return Object.entries(users) return Object.entries(users)
@ -71,35 +71,21 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
const encoded = req.header('Authorization').substring(5) const encoded = req.header('Authorization').substring(5)
const decoded = base64.decode(encoded).substring(1) const decoded = base64.decode(encoded).substring(1)
const hash = makeHash(decoded) const hash = makeHash(decoded)
console.log({ hash })
const event = { const event = {
user: apiGetUserId(hash) user: apiGetUserId(hash)
} }
const user = getUser(event.user) const user = await getUser(event.user)
if (user.name !== 'TEST-USER' && illegalCommands.includes(commandName?.toLowerCase())) { if (user.name !== 'TEST-USER' && illegalCommands.includes(commandName?.toLowerCase())) {
res.send('Command is illegal over the web api!') res.send('Command is illegal over the web api!')
return return
} }
if (!event.user) { if (!event.user) {
res.status(400) res.status(400)
res.send( res.send(`User with that password does not exist, or user does not have a password.
'User with that password does not exist, or user does not have a password.\n' + See '!setpw help' for assistance.\n`)
'See \'!setpw help\' for assistance.\n'
)
console.log(' bad password')
return 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') { if (words[1] === 'help') {
await say(commandNames.map(name => `\`${name}\``).join(', ') + ': ' + helpText) await say(commandNames.map(name => `\`${name}\``).join(', ') + ': ' + helpText)
@ -109,19 +95,19 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
return return
} }
const haunted = false 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) }) const canUse = await condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Admin) })
if (!canUse) { if (!canUse) {
await say(`Command '${words[0]}' not found`) await say(`Command '${words[0]}' not found`)
return return
} }
await action({ event, say, trueSay: say, words, args, commandName, user, userId: event.user, haunted }) await action({ event, say, trueSay: say, words, args, commandName, user, userId: event.user, haunted })
await saveUser(event.user, user, 'webapi action')
} catch (e) { } catch (e) {
console.error('route error', e) console.error('route error', e)
const example = "`curl --location-trusted -u ':your-pw' quacker.sagev.space/lb`" 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` + 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` + 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.`) 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' && commandNames.forEach(name => name !== '!!help' &&

View File

@ -1,4 +1,5 @@
const slack = require('../slack') const slack = require('../slack')
const { setJokes } = require('../games/hvacoins/utils')
// TODO: Move jokes/news into their own files, and let hvacker edit them when !addjoke or !addnews are used // TODO: Move jokes/news into their own files, and let hvacker edit them when !addjoke or !addnews are used
const jokes = [ const jokes = [
@ -106,3 +107,5 @@ module.exports = {
tellJoke, tellJoke,
newsAlert newsAlert
} }
setJokes(module.exports)

View File

@ -1,6 +1,7 @@
const { App: SlackApp } = require('@slack/bolt') const { App: SlackApp } = require('@slack/bolt')
const config = require('../config') const config = require('../config')
const fs = require('fs') const fs = require('fs')
const http = require('http')
const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr, setSlackUsers } = require('../games/hvacoins/utils') const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr, setSlackUsers } = require('../games/hvacoins/utils')
const temperatureChannelId = 'C034156CE03' const temperatureChannelId = 'C034156CE03'
@ -72,7 +73,7 @@ const buildSayPrepend = ({ say, prepend }) => async msg => {
}) })
} }
process.once('SIGINT', code => { process.once('SIGINT', () => {
saveGame('SIGINT', true) saveGame('SIGINT', true)
process.exit() process.exit()
}) })
@ -82,6 +83,9 @@ const activePolls = {}
const testId = 'U028BMEBWBV_TEST' const testId = 'U028BMEBWBV_TEST'
let testMode = false let testMode = false
app.event('message', async ({ event, context, client, say }) => { app.event('message', async ({ event, context, client, say }) => {
if (event.text?.startsWith('!!!')) {
return
}
if (event.subtype !== 'message_changed' && event?.text !== '!') { if (event.subtype !== 'message_changed' && event?.text !== '!') {
console.log('message.event', { console.log('message.event', {
...event, ...event,
@ -89,6 +93,16 @@ app.event('message', async ({ event, context, client, say }) => {
}) })
} }
if (event?.user === users.Admin) { 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 (event?.text.startsWith('!')) {
if (testMode) { if (testMode) {
await messageAdmin('Currently in test mode!') await messageAdmin('Currently in test mode!')
@ -108,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)
@ -184,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) {
@ -215,11 +231,19 @@ app.event('message', async ({ event, context, client, say }) => {
let text let text
if (hotterVotes > colderVotes && hotterVotes > contentVotes) { if (hotterVotes > colderVotes && hotterVotes > contentVotes) {
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 = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
}
text += 'raise the temperature, quack.' text += 'raise the temperature, quack.'
requestTempChange('Hotter') requestTempChange('Hotter')
} else if (colderVotes > hotterVotes && colderVotes > contentVotes) { } else if (colderVotes > hotterVotes && colderVotes > contentVotes) {
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 = `<@${users[users.ThermoController]}> The people have spoken, and would like to `
}
text += 'lower the temperature, quack quack.' text += 'lower the temperature, quack quack.'
requestTempChange('Colder') requestTempChange('Colder')
} else { } else {