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/ .idea/
backups/ backups/
hvacoins.json hvacoins.json
hvackerconfig.json
users.json
standup.json
# Logs # Logs
logs logs
*.log *.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", "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

@ -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 }) => {
@ -181,6 +190,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 +231,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,7 +239,7 @@ 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}`;
@ -210,42 +250,33 @@ const cardGames = {
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 +287,13 @@ 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()
console.log('arg', arg)
if (cardGame.cards && !cardGame.fetch) { if (cardGame.cards && !cardGame.fetch) {
const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase())) const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase()))
if (fileName) { if (fileName) {
@ -268,10 +301,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) {
@ -307,7 +343,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
@ -437,7 +473,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 })
@ -523,6 +558,7 @@ command(
) )
const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => { const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) => {
console.log('messageHandler')
if (event?.subtype === 'bot_message') { if (event?.subtype === 'bot_message') {
return botMessageHandler({ event, say }) return botMessageHandler({ event, say })
} }
@ -530,7 +566,8 @@ 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) console.log('getUser')
let user = await getUser(event.user)
if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) { if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) {
return return
} }
@ -573,7 +610,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
}) })
@ -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)) Object.entries(users).forEach(([id, usr]) => usr.coins = getCoins(id))
//user.coins = getCoins(event.user) //user.coins = getCoins(event.user)
const isAdmin = event.user?.includes(slack.users.Admin) const isAdmin = event.user?.includes(slack.users.Admin)
@ -635,6 +673,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 +687,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 +776,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 +786,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 +805,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 +815,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,
@ -795,7 +839,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 +960,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 +989,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 +1024,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 +1097,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.`);
@ -1112,7 +1154,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.`
} }
@ -1142,7 +1183,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 +1195,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 +1208,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)
@ -1381,7 +1422,7 @@ const upgradeButton = async ({ body, ack, say, payload }) => {
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 +1464,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 +1559,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.`)
} }
@ -1524,6 +1574,7 @@ command(
'Donate coins to a fellow player\n' + 'Donate coins to a fellow player\n' +
' Send coins by saying \'!gift @player coin_amount\'', ' Send coins by saying \'!gift @player coin_amount\'',
async ({ event, args, say, user, haunted }) => { async ({ event, args, say, user, haunted }) => {
return say(`I'm sorry, but you people can't be trusted anymore.`)
if (haunted) { if (haunted) {
return say(`!give doesn't work while you're haunted.`) return say(`!give doesn't work while you're haunted.`)
} }
@ -1559,7 +1610,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?')
@ -1696,14 +1747,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 +1764,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 +1804,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 +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('!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 +1845,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)
@ -1852,7 +1919,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` +
@ -1881,7 +1948,7 @@ command(
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 +1987,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)
@ -2202,7 +2269,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 +2328,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,8 +1,11 @@
const { Pool } = require('pg')
const fs = require('fs') const fs = require('fs')
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 dbPool = new Pool()
let jokes let jokes
let slackUsers let slackUsers
const setSlackUsers = users => { const setSlackUsers = users => {
@ -64,18 +67,38 @@ const makeBackup = () => {
fs.writeFileSync(fileName, JSON.stringify(game)) 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 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()
} }
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')
} }
}
// Object.entries(game.users).forEach(([userId, user]) => saveUser(userId, user))
fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2)) fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2))
} }
@ -217,7 +240,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
@ -253,7 +288,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 ??= {}
@ -264,8 +316,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]
} }
@ -276,8 +329,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
@ -577,8 +630,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,
@ -589,6 +660,7 @@ module.exports = {
addAchievement, addAchievement,
getCoins, getCoins,
getUser, getUser,
getUserSync,
singleItemCps, singleItemCps,
getCPS, getCPS,
getItemCps, getItemCps,
@ -617,5 +689,6 @@ module.exports = {
setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update, setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update,
setUpgrades, setUpgrades,
setSlackUsers, setSlackUsers,
setJokes: _jokes => jokes = _jokes 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,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'
@ -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!')
@ -215,11 +229,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 {