Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
|
161780c45f | |
|
71df29b27b | |
|
35e401cd0b | |
|
2d2e0c9368 | |
|
7bc1455e37 |
|
@ -1,6 +1,10 @@
|
|||
script.sh
|
||||
.idea/
|
||||
backups/
|
||||
hvacoins.json
|
||||
hvackerconfig.json
|
||||
users.json
|
||||
standup.json
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -14,7 +14,8 @@
|
|||
"base-64": "^1.0.0",
|
||||
"express": "^4.17.3",
|
||||
"fs": "0.0.1-security",
|
||||
"jest": "27.0.6"
|
||||
"jest": "27.0.6",
|
||||
"pg": "^8.11.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
@ -1800,6 +1801,14 @@
|
|||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-writer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
|
@ -4619,6 +4628,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/packet-reader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
|
@ -4691,6 +4705,89 @@
|
|||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||
"dependencies": {
|
||||
"buffer-writer": "2.0.0",
|
||||
"packet-reader": "1.0.0",
|
||||
"pg-connection-string": "^2.6.2",
|
||||
"pg-pool": "^3.6.1",
|
||||
"pg-protocol": "^1.6.0",
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pg-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -4739,6 +4836,41 @@
|
|||
"semver-compare": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
|
@ -5172,6 +5304,14 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
@ -5764,6 +5904,14 @@
|
|||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
@ -7132,6 +7280,11 @@
|
|||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
|
||||
},
|
||||
"buffer-writer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
|
@ -9067,6 +9220,11 @@
|
|||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
||||
},
|
||||
"packet-reader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
|
@ -9113,6 +9271,68 @@
|
|||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"pg": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
||||
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
||||
"requires": {
|
||||
"buffer-writer": "2.0.0",
|
||||
"packet-reader": "1.0.0",
|
||||
"pg-cloudflare": "^1.1.1",
|
||||
"pg-connection-string": "^2.6.2",
|
||||
"pg-pool": "^3.6.1",
|
||||
"pg-protocol": "^1.6.0",
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x"
|
||||
}
|
||||
},
|
||||
"pg-cloudflare": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||
"optional": true
|
||||
},
|
||||
"pg-connection-string": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
||||
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
||||
},
|
||||
"pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
|
||||
},
|
||||
"pg-pool": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
||||
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
||||
"requires": {}
|
||||
},
|
||||
"pg-protocol": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||
},
|
||||
"pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"requires": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"requires": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -9144,6 +9364,29 @@
|
|||
"semver-compare": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
|
||||
},
|
||||
"postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
|
||||
},
|
||||
"postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
|
||||
},
|
||||
"postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"requires": {
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
|
@ -9427,6 +9670,11 @@
|
|||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"split2": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
@ -9816,6 +10064,11 @@
|
|||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
"base-64": "^1.0.0",
|
||||
"express": "^4.17.3",
|
||||
"fs": "0.0.1-security",
|
||||
"jest": "27.0.6"
|
||||
"jest": "27.0.6",
|
||||
"pg": "^8.11.3"
|
||||
},
|
||||
"standard": {
|
||||
"env": "jest"
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
export PGHOST=localhost
|
||||
export PGUSER="postgres"
|
||||
export PGDATABASE="postgres"
|
||||
export PGPASSWORD="thisisthesoundofamannamedpostgres"
|
||||
export PGPORT=5432
|
||||
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const buyableItems = require('./buyableItems')
|
||||
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher, calculateCost } = require('./utils')
|
||||
const { commas, setHighestCoins, addAchievement, getUser, singleItemCps, chaosFilter, fuzzyMatcher, calculateCost, saveUser } = require('./utils')
|
||||
const slack = require('../../slack')
|
||||
|
||||
const leaderboardUpdater = {}
|
||||
|
@ -153,6 +153,7 @@ const buyRoute = async ({ event, say, args, user }) => {
|
|||
|
||||
const countString = quantity === 1 ? 'one' : quantity
|
||||
await say(`You bought ${countString} :${buyableItem.emoji}:`)
|
||||
await saveUser(event.user, user, `Buying ${countString} :${buyableItem.emoji}:`)
|
||||
}
|
||||
|
||||
const buyButton = async ({ body, ack, say, payload }) => {
|
||||
|
@ -162,7 +163,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
|||
const event = {
|
||||
user: body.user.id
|
||||
}
|
||||
const user = getUser(event.user)
|
||||
const user = await getUser(event.user)
|
||||
const words = ['', buying, body.actions[0].text]
|
||||
const [commandName, ...args] = words
|
||||
|
||||
|
@ -177,6 +178,7 @@ const buyButton = async ({ body, ack, say, payload }) => {
|
|||
...buyText2(highestCoins, user, extraMessage)
|
||||
})
|
||||
await leaderboardUpdater.updateAllLeaderboards()
|
||||
await saveUser(event.user, user, `buyButton ${buying} clicked`)
|
||||
}
|
||||
|
||||
Object.keys(buyableItems).forEach(itemName => slack.app.action('buy_' + itemName, buyButton))
|
||||
|
|
|
@ -136,7 +136,7 @@ const addInteraction = ({ actionId, perform }) =>
|
|||
try {
|
||||
await ack()
|
||||
game.pet ??= makePet()
|
||||
const [everyone, local] = perform(game.pet, getUser(body.user.id))
|
||||
const [everyone, local] = perform(game.pet, await getUser(body.user.id))
|
||||
await updateEveryone(everyone)
|
||||
if (local) {
|
||||
await say(local)
|
||||
|
|
|
@ -8,6 +8,8 @@ const {
|
|||
idFromWord,
|
||||
getCoins,
|
||||
getUser,
|
||||
getUserSync,
|
||||
saveUser,
|
||||
commas,
|
||||
addAchievement,
|
||||
shufflePercent,
|
||||
|
@ -25,13 +27,15 @@ const {
|
|||
userHasCheckedQuackgrade,
|
||||
fuzzyMatcher,
|
||||
addCoins,
|
||||
game, updateAll
|
||||
game,
|
||||
updateAll,
|
||||
logMemoryUsage
|
||||
} = require('./utils')
|
||||
const { nfts, squad, users, horrors, stonkMarket, pet } = game
|
||||
const pets = require('./gotcha')
|
||||
|
||||
const exec = require('child_process').exec
|
||||
const { createReadStream, createWriteStream, existsSync, readFileSync} = require('fs')
|
||||
const { createReadStream, createWriteStream, existsSync, readFileSync, writeFileSync } = require('fs')
|
||||
const { readdir } = require('fs/promises')
|
||||
|
||||
const slack = require('../../slack')
|
||||
|
@ -80,7 +84,9 @@ const upgradeText = (user, showOwned = false) => {
|
|||
const hasUpgrade = (user, upgrade, upgradeName) => !!user.upgrades[upgrade.type]?.includes(upgradeName)
|
||||
|
||||
const alwaysAccessible = () => true
|
||||
|
||||
const alwaysAlwaysAccessible = () => true
|
||||
|
||||
const adminOnly = {
|
||||
hidden: true,
|
||||
condition: ({ event, say }) => {
|
||||
|
@ -91,10 +97,12 @@ const adminOnly = {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const testOnly = {
|
||||
hidden: true,
|
||||
condition: ({ event }) => event.user.includes('TEST')
|
||||
}
|
||||
|
||||
const dmsOnly = {
|
||||
hidden: false,
|
||||
condition: async ({ event, say, commandName }) => {
|
||||
|
@ -105,6 +113,7 @@ const dmsOnly = {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const prestigeOnly = {
|
||||
hidden: false,
|
||||
condition: async ({ event, say, commandName, user }) => {
|
||||
|
@ -131,11 +140,9 @@ const defaultAccess = { hidden: false, condition: alwaysAccessible }
|
|||
*/
|
||||
const command = (commandNames, helpText, action, { hidden, condition } = defaultAccess) => {
|
||||
if (!hidden) {
|
||||
console.log(`Initializing command '${commandNames[0]}'`)
|
||||
commandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')} - ${helpText}\n`
|
||||
shortCommandHelpText += `\n${commandNames.toString().replace(/,/g, ', ')}`
|
||||
} else if (condition === adminOnly.condition) {
|
||||
console.log(`Initializing admin command '${commandNames[0]}'`)
|
||||
} else {
|
||||
hiddenCommands++
|
||||
}
|
||||
|
@ -181,6 +188,37 @@ const postCard = async (event, name, fileName) =>
|
|||
file: createReadStream(fileName)
|
||||
})
|
||||
|
||||
const findLineStartingWith = (prefix, text) => {
|
||||
const lines = text.split(/\s*\n\s*/g)
|
||||
const line = lines.filter(line => line.startsWith(prefix))[0]
|
||||
if (!line) {
|
||||
return
|
||||
}
|
||||
return line.substring(prefix.length)
|
||||
}
|
||||
|
||||
const findLinesNotStartingWith = (prefixes, text) => {
|
||||
const lines = text.split(/\s*\n\s*/g)
|
||||
const lineStartsWithAnyPrefix = line => prefixes.some(prefix => line.startsWith(prefix))
|
||||
return lines.filter(line => !lineStartsWithAnyPrefix(line)).join('\n')
|
||||
}
|
||||
|
||||
const isaacData = (() => {
|
||||
const parsed = JSON.parse(readFileSync('isaac-processed.json'))
|
||||
parsed.allItems = [...parsed.items, ...parsed.cards, ...parsed.trinkets]
|
||||
parsed.allItems = Object.fromEntries(parsed.allItems.map(item => [item.name.toLowerCase().trim(), {
|
||||
...item,
|
||||
itemId: findLineStartingWith('ItemID: ', item.text),
|
||||
quality: findLineStartingWith('Quality: ', item.text),
|
||||
type: findLineStartingWith('Type: ', item.text),
|
||||
rechargeTime: findLineStartingWith('Recharge Time: ', item.text),
|
||||
itemPools: findLineStartingWith('Item Pool: ', item.text)?.split(', '),
|
||||
description: findLinesNotStartingWith(['ItemID', 'Quality: ', 'Type: ', 'Recharge Time: ', 'Item Pool: '], item.text)
|
||||
}]))
|
||||
writeFileSync('isaac-new.json', JSON.stringify(parsed.allItems, null, 2))
|
||||
return parsed
|
||||
})()
|
||||
|
||||
const cardGames = {
|
||||
digimon: {
|
||||
names: ['!digimon', '!digi'],
|
||||
|
@ -191,7 +229,7 @@ const cardGames = {
|
|||
getCardImageUrl: card => card.image_url
|
||||
},
|
||||
pokemon: {
|
||||
names: ['!pokemon', '!poke', '!pok', '!yugioh', '!ygo'],
|
||||
names: ['!pokemon', '!poke', '!pok'],
|
||||
help: 'Search for Pokemon cards: !pok <card name>',
|
||||
fetch: async name => `https://api.pokemontcg.io/v2/cards?q=name:${name}`,
|
||||
getCardData: ({ data }) => data,
|
||||
|
@ -199,53 +237,43 @@ const cardGames = {
|
|||
getCardImageUrl: card => card.images.large
|
||||
},
|
||||
yugioh: {
|
||||
names: [],
|
||||
names: ['!yugioh', '!ygo'],
|
||||
help: 'Search for Yu-Gi-Oh cards: !ygo <card name>',
|
||||
fetch: async name => {
|
||||
const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?fname=${name}`;
|
||||
console.log('yugioh url', url)
|
||||
return url
|
||||
},
|
||||
getCardData: ({ data }) => data,
|
||||
getCardName: card => card.name,
|
||||
getCardImageUrl: card => card.card_images[0].image_url
|
||||
},
|
||||
// lotrOld: {
|
||||
// names: ['!lotrOld'],
|
||||
// help: 'Search for Lord of the Rings cards: !lotrOld <card name>',
|
||||
// cards: async () => JSON.parse(readFileSync('lotrOld/lotr_cards.json').toString()),
|
||||
// fetch: async name => ({ json: () => {
|
||||
// const matcher = fuzzyMatcher(name?.toLowerCase())
|
||||
// const exact = cardGames.lotrOld.cards.filter(card => card.name?.toLowerCase() === name.toLowerCase())
|
||||
// if (exact.length) {
|
||||
// return exact
|
||||
// }
|
||||
// return cardGames.lotrOld.cards.filter(card => matcher.test(card.name))
|
||||
// } }),
|
||||
// getCardData: data => data,
|
||||
// getCardName: card => card.name,
|
||||
// getCardImageUrl: card => 'https://ringsdb.com' + card.imagesrc
|
||||
// },
|
||||
// lotr: {
|
||||
// names: ['!lotr'],
|
||||
// help: 'Search for Lord of the Rings cards: !lotr <card name>',
|
||||
// cards: async () => JSON.parse(readFileSync('lotr/cards.json').toString()),
|
||||
// fetch: async name => ({ json: () => {
|
||||
// const matcher = fuzzyMatcher(name?.toLowerCase())
|
||||
// const exact = cardGames.lotr.cards.filter(card => card.name?.toLowerCase() === name.toLowerCase())
|
||||
// if (exact.length) {
|
||||
// return [exact[0]]
|
||||
// }
|
||||
// return cardGames.lotr.cards.filter(card => matcher.test(card.name)).filter(card => !card.name.includes('('))
|
||||
// } }),
|
||||
// getCardData: data => data,
|
||||
// getCardName: card => card.name,
|
||||
// getCardImageUrl: card => 'https://lotrtcgwiki.com/wiki/_media/cards:' + card.id + '.jpg'
|
||||
// },
|
||||
playingCards: {
|
||||
names: ['!playing', '!pc'],
|
||||
help: 'Search for playing cards cards: !pc <card name>',
|
||||
cards: async () => readdir('playingCards')
|
||||
},
|
||||
isaac: {
|
||||
names: ['!isaac', '!tboi'],
|
||||
help: 'Search for TBOI items, cards, and trinkets',
|
||||
cards: async () => Object.keys(isaacData.allItems),
|
||||
fetch: async (name, say) => {
|
||||
name = name.toLowerCase()
|
||||
let matches = [isaacData.allItems[name]].filter(Boolean)
|
||||
if (!matches.length) {
|
||||
matches = Object.values(isaacData.allItems).filter(item => item.name.toLowerCase().includes(name))
|
||||
}
|
||||
if (!matches.length) {
|
||||
matches = Object.values(isaacData.allItems).filter(item => item.text.toLowerCase().includes(name))
|
||||
}
|
||||
//say('```' + JSON.stringify(matches, null, 2) + '```')
|
||||
if (!matches.length) {
|
||||
await say('No matches found!')
|
||||
return
|
||||
}
|
||||
const etcText = matches.length == 1 ? "" : `and ${matches.length - 1} other matches`
|
||||
const match = matches[0]
|
||||
await say(match.name + ': ' + match.description.replaceAll('\t', '').replaceAll(/ +/g, '').replaceAll(/\n\*,.*/g, ''))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,11 +284,12 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
|
|||
command(
|
||||
cardGame.names,
|
||||
cardGame.help,
|
||||
async ({ args, event, say }) => {
|
||||
const arg = args?.join(' ')
|
||||
if (!args?.length || !arg) {
|
||||
return say('Please specify a card name!')
|
||||
}
|
||||
async ({ args, event, say }) => args?.join(' ').split(',').forEach(async arg => {
|
||||
// const arg = args?.join(' ')
|
||||
// if (!args?.length || !arg) {
|
||||
// return say('Please specify a card name!')
|
||||
// }
|
||||
arg = arg.trim()
|
||||
if (cardGame.cards && !cardGame.fetch) {
|
||||
const fileName = cardGame.cards.find(name => name?.toLowerCase().replaceAll(/_/g, ' ').startsWith(arg.toLowerCase()))
|
||||
if (fileName) {
|
||||
|
@ -268,10 +297,13 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
|
|||
}
|
||||
return
|
||||
}
|
||||
let response = await cardGame.fetch(arg)
|
||||
let response = await cardGame.fetch(arg, say)
|
||||
if (typeof response === 'string') {
|
||||
response = await fetch(response)
|
||||
}
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
const json = await response.json()
|
||||
const data = cardGame.getCardData(json)
|
||||
if (!data?.length) {
|
||||
|
@ -290,7 +322,6 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
|
|||
const name = cardGame.getCardName(card)
|
||||
const fileName = gameName + '/' + name
|
||||
if (existsSync(fileName)) {
|
||||
console.log(`Using cached file: ${fileName}`)
|
||||
return postCard(event, name, fileName)
|
||||
}
|
||||
const file = createWriteStream(fileName)
|
||||
|
@ -298,7 +329,6 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
|
|||
response.pipe(file)
|
||||
file.on('finish', async () => {
|
||||
await file.close()
|
||||
console.log(event.channel)
|
||||
await postCard(event, name, fileName)
|
||||
}).on('error', err => {
|
||||
console.error(err)
|
||||
|
@ -307,7 +337,7 @@ Object.entries(cardGames).forEach(async ([gameName, cardGame]) => {
|
|||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
hidden: false,
|
||||
condition: alwaysAlwaysAccessible
|
||||
|
@ -338,7 +368,7 @@ command(
|
|||
|
||||
command(
|
||||
['!in'],
|
||||
'!in <channel> <message>',
|
||||
'Post a message in a specific channel: !in <channel> <message>',
|
||||
async ({ args, event }) => {
|
||||
const channel = idFromWord(args[0])
|
||||
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 { user, text } = event
|
||||
|
||||
const payload = {
|
||||
event: {
|
||||
text: event.text,
|
||||
user: event.user
|
||||
text,
|
||||
user
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
...msg,
|
||||
text: slack.encodeData('commandPayload', payload) + msg.text
|
||||
})
|
||||
}
|
||||
|
||||
command(['!ping'], 'Ping', async ({ say }) => say('Hello!'), adminOnly)
|
||||
|
||||
const userHasTheGift = user => userHasCheckedQuackgrade(user, 'theGift')
|
||||
|
||||
command(
|
||||
|
@ -437,7 +535,6 @@ command(
|
|||
return say(`Silly, silly, ${user.name}.\nYou can't just leave us again.`)
|
||||
}
|
||||
user.isDisabled = true
|
||||
//saveGame()
|
||||
return say('.')
|
||||
}, { hidden: true })
|
||||
|
||||
|
@ -455,7 +552,6 @@ const noWinner = 'NO WINNER'
|
|||
const getPollWinner = async ({ channel, ts }) => {
|
||||
try {
|
||||
const msg = await slack.getMessage({ channel, ts })
|
||||
console.log('pollWinner message', JSON.stringify(msg.messages[0]))
|
||||
let texts = []
|
||||
let maxVotes = 0
|
||||
for (let i = 1; i < msg.messages[0].blocks.length; i++) {
|
||||
|
@ -465,11 +561,8 @@ const getPollWinner = async ({ channel, ts }) => {
|
|||
continue
|
||||
}
|
||||
votes = votes.split('@').length - 1
|
||||
console.log(`${votes} votes for:`)
|
||||
text = text.replace(/^\s*:[a-z]*: /, '')
|
||||
text = text.replace(/\s+`\d+`$/, '')
|
||||
console.log(`TEXT: '${text}'`)
|
||||
console.log(``)
|
||||
if (votes > maxVotes) {
|
||||
maxVotes = votes
|
||||
texts = [text]
|
||||
|
@ -477,7 +570,6 @@ const getPollWinner = async ({ channel, ts }) => {
|
|||
texts.push(text)
|
||||
}
|
||||
}
|
||||
console.log('TEXTS', texts)
|
||||
if (texts.length === 1) {
|
||||
return [texts[0], false]
|
||||
} else if (texts.length > 1) {
|
||||
|
@ -517,7 +609,6 @@ command(
|
|||
async ({ args, say, user }) => {
|
||||
try {
|
||||
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)}
|
||||
}
|
||||
)
|
||||
|
@ -530,7 +621,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
|
|||
const words = event?.text?.split(/\s+/) || []
|
||||
const [commandName, ...args] = words
|
||||
const c = commands.get(commandName)
|
||||
let user = getUser(event.user)
|
||||
let user = await getUser(event.user)
|
||||
if (user.isDisabled && c.condition !== alwaysAlwaysAccessible) {
|
||||
return
|
||||
}
|
||||
|
@ -573,7 +664,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
|
|||
haunted = true
|
||||
const [disabledId] = getRandomFromArray(disabledUsers)
|
||||
event.user = disabledId
|
||||
user = getUser(event.user)
|
||||
user = await getUser(event.user)
|
||||
const userInfo = await slack.app.client.users.info({
|
||||
user: disabledId
|
||||
})
|
||||
|
@ -635,6 +726,7 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
|
|||
return
|
||||
}
|
||||
|
||||
const before = JSON.stringify(user)
|
||||
await c.action({ event, say, trueSay, words, args, commandName, user, userId: event.user, haunted, isAdmin })
|
||||
if (!isRecycle) {
|
||||
const userQuackgrades = (user.quackUpgrades?.lightning || []).map(name => quackStore[name])
|
||||
|
@ -648,8 +740,13 @@ const messageHandler = async ({ event, say, isRecycle = false, skipCounting }) =
|
|||
setTimeout(() => lightning({ channel: event.channel, say, trueSay, words, user }), 10000)
|
||||
}
|
||||
}
|
||||
const after = JSON.stringify(user)
|
||||
const endTime = new Date()
|
||||
saveGame(`command ${event.text} finished in ${endTime - startTime}ms`)
|
||||
if (before !== after) {
|
||||
await saveUser(event.user, user, `command ${event.text} finished in ${endTime - startTime}ms`)
|
||||
} else {
|
||||
console.error(event.user, user, `command ${event.text} requested a redundant user save!`)
|
||||
}
|
||||
}
|
||||
|
||||
slack.onReaction(async ({ event }) => {
|
||||
|
@ -732,7 +829,7 @@ command(
|
|||
'Send a lighting strike to the given player.',
|
||||
async({ args, say, }) => {
|
||||
const targetId = idFromWord(args[0])
|
||||
await lightning({ user: getUser(targetId), ms: 15000, channel: targetId})
|
||||
await lightning({ user: await getUser(targetId), ms: 15000, channel: targetId})
|
||||
return say(`Sent a bolt of lighting to <@${targetId}>`)
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -742,14 +839,14 @@ command(
|
|||
async ({ say, args }) => {
|
||||
// await dedicatedPlayers.forEach(async player => {
|
||||
// await lightning({
|
||||
// user: getUser(player),
|
||||
// user: await getUser(player),
|
||||
// ms: 30000,
|
||||
// channel: player
|
||||
// })
|
||||
// })
|
||||
const targetId = idFromWord(args[0])
|
||||
for (let i = 0; i < 10; i++) {
|
||||
setTimeout(async () => await lightning({ user: getUser(targetId), ms: 1600, channel: targetId, multiplier: 0.02}), i * 1500)
|
||||
setTimeout(async () => await lightning({ user: await getUser(targetId), ms: 1600, channel: targetId, multiplier: 0.02}), i * 1500)
|
||||
}
|
||||
return say(`Sent a lighting storm to <@${targetId}>`)
|
||||
// return say(`Sent a bolt of lighting to the dedicated players`)
|
||||
|
@ -761,7 +858,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
|||
return
|
||||
}
|
||||
const c = getCoins(body.user.id)
|
||||
const user = getUser(body.user.id)
|
||||
const user = await getUser(body.user.id)
|
||||
const secondsOfCps = seconds => Math.floor(getCPS(user) * seconds)
|
||||
let payout = secondsOfCps(60 * 30)
|
||||
if (payout > (0.2 * c)) {
|
||||
|
@ -771,7 +868,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
|||
payout = (500 + chaosFilter(payout, 1, user)) * strikes[body.message.ts]
|
||||
addCoins(user, (c + payout) - user.coins)
|
||||
delete strikes[body.message.ts]
|
||||
saveGame('user bottled a lightning strike')
|
||||
saveUser(body.user.id, user, 'bottled a lightning strike')
|
||||
|
||||
await slack.app.client.chat.update({
|
||||
channel: body.channel.id,
|
||||
|
@ -780,7 +877,7 @@ slack.app.action('lightningStrike', async ({ body, ack }) => {
|
|||
blocks: []
|
||||
})
|
||||
await ack()
|
||||
return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`)
|
||||
// return slack.messageAdmin(`Lighting bottled by <@${body.user.id}>`)
|
||||
})
|
||||
|
||||
slack.onMessage(async msg => {
|
||||
|
@ -795,7 +892,7 @@ command(
|
|||
['!cleanusers'],
|
||||
'Calls getUser() on all users, ensuring a valid state.',
|
||||
async ({ say }) => {
|
||||
Object.keys(users).forEach(userId => { getUser(userId) })
|
||||
Object.keys(users).forEach(async userId => { await getUser(userId) })
|
||||
return say('```Cleaning ' + JSON.stringify(Object.keys(users), null, 2) + '```')
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -916,7 +1013,6 @@ command(
|
|||
return say(`Your password may not contain spaces!`)
|
||||
}
|
||||
user.pwHash = webapi.makeHash(args[0])
|
||||
//saveGame()
|
||||
await say(`Password encoded as ${user.pwHash}`)
|
||||
}
|
||||
, { hidden: true })
|
||||
|
@ -946,7 +1042,6 @@ command(
|
|||
user.coins -= cost
|
||||
horrors.hvackerHelp += 1
|
||||
horrors.hvackerLast = dayOfYear()
|
||||
//saveGame()
|
||||
await say('I feel a bit better. Thank you...')
|
||||
}, { hidden: true })
|
||||
|
||||
|
@ -982,7 +1077,7 @@ command(
|
|||
async ({ say, args }) => {
|
||||
const achName = args[0]
|
||||
const target = idFromWord(args[1])
|
||||
await removeAchievement(getUser(target), achName, say)
|
||||
await removeAchievement(await getUser(target), achName, say)
|
||||
}, adminOnly)
|
||||
|
||||
command(
|
||||
|
@ -1055,7 +1150,7 @@ command(['!cps'],
|
|||
// }
|
||||
// } catch (e) {}
|
||||
//
|
||||
// const text = await doMine({ user: getUser(event.user), userId: event.user, say })
|
||||
// const text = await doMine({ user: await getUser(event.user), userId: event.user, say })
|
||||
// console.log('miningHeat:', miningHeat)
|
||||
// if (miningHeat < maxTemp) {
|
||||
// console.log(`${slack.users[event.user]} is pick mining.`);
|
||||
|
@ -1097,7 +1192,7 @@ const doMine = async ({ user, userId, say }) => {
|
|||
diff = 500 + secondsOfCps(60 * 60, 0.2)
|
||||
prefix = `:gem: You found a lucky gem worth ${commas(diff)} HVAC!\n`
|
||||
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) {
|
||||
diff = 50 + secondsOfCps(60 * 5, 0.1)
|
||||
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`
|
||||
}
|
||||
addCoins(user, diff)
|
||||
//saveGame()
|
||||
return `${prefix}You now have ${commas(user.coins)} HVAC coin${c !== 1 ? 's' : ''}. Spend wisely.`
|
||||
}
|
||||
|
||||
|
@ -1122,7 +1216,7 @@ command(
|
|||
'Mine HVAC coins',
|
||||
async ({ say, user, userId }) => {
|
||||
await say(await doMine({ user, userId, say }))
|
||||
if ((lbIndex++) % 20 == 0) {
|
||||
if ((lbIndex++) % 100 == 0) {
|
||||
return updateAllLeaderboards()
|
||||
}
|
||||
}
|
||||
|
@ -1142,7 +1236,7 @@ command(
|
|||
async ({ event, args, trueSay }) => {
|
||||
const [impersonating, ...newWords] = args
|
||||
event.user = idFromWord(impersonating)
|
||||
getUser(event.user)
|
||||
await getUser(event.user)
|
||||
const isDisabled = users[event.user].isDisabled
|
||||
users[event.user].isDisabled = false
|
||||
event.text = newWords.join(' ')
|
||||
|
@ -1154,7 +1248,7 @@ command(
|
|||
['!enable'],
|
||||
'Enable the given user',
|
||||
async ({ args }) => {
|
||||
const user = getUser(idFromWord(args[0]))
|
||||
const user = await getUser(idFromWord(args[0]))
|
||||
if (user.isDisabled) {
|
||||
user.isDisabled = false
|
||||
//saveGame()
|
||||
|
@ -1167,7 +1261,7 @@ command(
|
|||
['!disable'],
|
||||
'Disable the given user',
|
||||
async ({ args }) => {
|
||||
const user = getUser(idFromWord(args[0]))
|
||||
const user = await getUser(idFromWord(args[0]))
|
||||
user.isDisabled = true
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -1216,7 +1310,6 @@ command(
|
|||
} else {
|
||||
outcome = 'lost'
|
||||
}
|
||||
console.log(`They ${outcome}`)
|
||||
//saveGame()
|
||||
await say(`You bet ${commas(n)} coins and ${outcome}! You now have ${commas(user.coins)}.`)
|
||||
if (outcome === 'lost' && user.lostBetMessage) {
|
||||
|
@ -1225,7 +1318,7 @@ command(
|
|||
await trueSay(user.wonBetMessage)
|
||||
}
|
||||
return updateAllLeaderboards()
|
||||
}
|
||||
}, dmsOnly
|
||||
)
|
||||
|
||||
const emojiRegex = /^:[^:\s]*:$/
|
||||
|
@ -1237,7 +1330,6 @@ const validEmoji = async emojiText => {
|
|||
const validEmojis = (await getEmojis()).emoji
|
||||
const noColons = emojiText.replace(/:/g, '')
|
||||
|
||||
// console.log('validEmojis', validEmojis)
|
||||
return !!validEmojis[noColons]
|
||||
}
|
||||
const getEmojis = async () => await slack.app.client.emoji.list()
|
||||
|
@ -1327,7 +1419,6 @@ command(
|
|||
if (!args[0]) {
|
||||
return say(upgradeText2(user))
|
||||
}
|
||||
console.log({args: args.join(' ')})
|
||||
const matcher = fuzzyMatcher(args.join(' '))
|
||||
const u = Object.entries(upgrades).find(([name, upgrade]) => matcher.test(name) || matcher.test(upgrade.name))
|
||||
if (!u) {
|
||||
|
@ -1377,11 +1468,10 @@ const upgradeBlock = upgradeName => {
|
|||
const upgradeButton = async ({ body, ack, say, payload }) => {
|
||||
await ack()
|
||||
const upgrade = payload.action_id.substring(8)
|
||||
console.log(`upgradeButton ${upgrade} clicked`)
|
||||
const event = {
|
||||
user: body.user.id
|
||||
}
|
||||
const user = getUser(event.user, true)
|
||||
const user = await getUser(event.user, true)
|
||||
const words = ['!upgrade', upgrade]
|
||||
const [commandName, ...args] = words
|
||||
let extraMessage = ''
|
||||
|
@ -1423,9 +1513,18 @@ const squadText = () => {
|
|||
`:${emoji}: *${name}* - ${commas(remaining)} HVAC remaining.\n_${description}_`
|
||||
return currentUpgradeText(current)
|
||||
}
|
||||
return 'No more squadgrades currently available.'
|
||||
return 'All squadgrades are unlocked!\n\n' + Object.values(squadUpgrades).map(({ name, cost, emoji, description }) =>
|
||||
`:${emoji}: *${name}* - ${commas(cost)} HVAC.\n_${description}_`).join('\n\n')
|
||||
}
|
||||
|
||||
command(
|
||||
['!cat'],
|
||||
'View your total all-time coins collected',
|
||||
async ({ say, user }) => {
|
||||
await say(`You've earned a total of ${commas(user.coinsAllTime)} HVAC`)
|
||||
}
|
||||
)
|
||||
|
||||
command(
|
||||
['!squad', '!sq'],
|
||||
'Buy upgrades that help the whole team.\n' +
|
||||
|
@ -1509,7 +1608,7 @@ command(
|
|||
const humanMembers = members.filter(name => name.length === 11)
|
||||
return say(`Hvacker owns ${humanMembers.length} souls.`)
|
||||
}
|
||||
const user = getUser(targetId)
|
||||
const user = await getUser(targetId)
|
||||
if (user.isDisabled) {
|
||||
return say(`<@${targetId}> is no longer with us.`)
|
||||
}
|
||||
|
@ -1559,7 +1658,7 @@ command(
|
|||
}
|
||||
let gifted = []
|
||||
for (const targetId of targets) {
|
||||
const targetUser = getUser(targetId)
|
||||
const targetUser = await getUser(targetId)
|
||||
user.coins -= individualAmount
|
||||
if (user.coinsAllTime < 10000) {
|
||||
// return say('Let \'em play for a bit, ay?')
|
||||
|
@ -1573,13 +1672,55 @@ command(
|
|||
const last = gifted.pop()
|
||||
recipients = gifted.map(t => users[t].name).join(', ') + ', and ' + users[last].name
|
||||
} else {
|
||||
console.log('gifted', gifted)
|
||||
console.log('users[gifted[0]]', users[gifted[0]])
|
||||
recipients = users[gifted[0]].name
|
||||
console.log('recipients', 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 = '') => {
|
||||
|
@ -1696,14 +1837,15 @@ command(
|
|||
})
|
||||
|
||||
const strike = user => user.isDisabled ? '~' : ''
|
||||
const struck = (user, string) => strike(user) + string + strike(user)
|
||||
|
||||
const generateLeaderboard = ({ args }) => {
|
||||
const generateLeaderboard = ({ args }, showCat) => {
|
||||
let index = 1
|
||||
return Object.entries(users)
|
||||
.filter(([, user]) => (!user.isDisabled || args[0] === 'all') && (Object.entries(user.items).length > 0 || user.prestige))
|
||||
.filter(([, user]) => (!user.isDisabled || args.includes('all')) && user.name && !['Hvacker', '???', 'TEST-USER'].includes(user.name))
|
||||
.sort(([id, user1], [id2, user2]) => {
|
||||
const leftPrestige = getUser(id).prestige
|
||||
const rightPrestige = getUser(id2).prestige
|
||||
const leftPrestige = getUserSync(id).prestige
|
||||
const rightPrestige = getUserSync(id2).prestige
|
||||
if (leftPrestige > rightPrestige) {
|
||||
return -1
|
||||
}
|
||||
|
@ -1712,21 +1854,22 @@ const generateLeaderboard = ({ args }) => {
|
|||
}
|
||||
return getCPS(user1) > getCPS(user2)
|
||||
})
|
||||
.map(([id, u]) => `${strike(u)}${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUser(id)))} CPS - ${commas(getCoins(id))} HVAC${strike(u)}`)
|
||||
.map(([id, u]) => `${strike(u)}${index++}. ${slack.users[id] || '???'} ${prestigeEmoji(u) || '-'} ${commas(getCPS(getUserSync(id)))} CPS - ${commas(getCoins(id))} HVAC${showCat ? ` (${commas(users[id].coinsAllTime)} all-time)` : ''}${strike(u)}`)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
command(
|
||||
['!leaderboard', '!lb'],
|
||||
'Show the top HVAC-earners, ranked by prestige, then CPS',
|
||||
async ({ say, user, args }) => {
|
||||
async ({ say, user, args, event }) => {
|
||||
// if ((event.user === slack.users.Houston || event.user === slack.users.Admin) && event.channel_type.includes('im')) {
|
||||
// return say('```' + `Hvacker - 9 souls - Taking from them whatever it desires\nSome other losers - who cares - whatever` + '```')
|
||||
// }
|
||||
game.leaderboardChannels ??= {}
|
||||
await say(generateLeaderboard({ args })).then(({ channel, ts }) => {
|
||||
const showCat = event.user === slack.users.Admin && args.includes('cat')
|
||||
await say(generateLeaderboard({ args }, showCat)).then(({ channel, ts }) => {
|
||||
addAchievement(user, 'leaderBoardViewer', say)
|
||||
if (args[0] === 'all') {
|
||||
if (args.includes('all') || showCat) {
|
||||
return
|
||||
}
|
||||
game.leaderboardChannels[channel] = ts
|
||||
|
@ -1751,7 +1894,20 @@ const oneShot = (name, helpText, message, achievementName) => {
|
|||
|
||||
command(names, helpText, async ({ say, user }) => {
|
||||
await say(message)
|
||||
await slack.messageAdmin(`Wow buddy they like your ${name} joke.`)
|
||||
if (achievementName) {
|
||||
addAchievement(user, achievementName, say)
|
||||
}
|
||||
}, { hidden: true })
|
||||
}
|
||||
|
||||
const oneShotFile = async (name, helpText, text, filePath, achievementName) => {
|
||||
const names = Array.isArray(name) ? name : [name]
|
||||
command(names, helpText, async ({ event }) => {
|
||||
await slack.app.client.files.upload({
|
||||
channels: event.channel,
|
||||
initial_comment: text,
|
||||
file: createReadStream(filePath)
|
||||
})
|
||||
if (achievementName) {
|
||||
addAchievement(user, achievementName, say)
|
||||
}
|
||||
|
@ -1759,15 +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('!santa', 'Ho ho ho!', '<https://i.imgur.com/dBWgFfQ.png|I\'m Santa Quacks>')
|
||||
oneShotFile('!santa', 'Ho ho ho!', '<https://i.imgur.com/dBWgFfQ.png|I\'m Santa Quacks>')
|
||||
oneShot('!sugma', 'Not very original.', ':hvacker_angery:')
|
||||
oneShot('!pog', 'One poggers hvacker', '<https://i.imgur.com/XCg7WDz.png|poggers>')
|
||||
oneShot('!ligma', 'Not very original.', '<https://i.imgur.com/i1YtW7m.png|no>')
|
||||
oneShot(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', '<https://i.imgur.com/FKYdeqo.jpg|I go XD style>', 'certifiedCoolGuy')
|
||||
oneShot('!based', 'Sorry, it\'s a little hard to hear you!', '<https://i.imgur.com/IUX6R26.png|What?>')
|
||||
oneShot('!shrek', 'Is love and is life.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
|
||||
oneShot('!sugondese', 'I don\'t like you.', '<https://i.imgur.com/VCvfvdz.png|rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>')
|
||||
oneShotFile('!pog', 'One poggers hvacker', 'poggers', 'images/XCg7WDz.png')
|
||||
oneShotFile('!ligma', 'Not very original.', 'no', 'images/i1YtW7m.png')
|
||||
oneShotFile(['!dab', '!dabs'], 'ACTIVATE COOL GUY MODE', 'I go XD style', 'images/FKYdeqo.jpg', 'certifiedCoolGuy')
|
||||
oneShotFile('!based', 'Sorry, it\'s a little hard to hear you!', 'What?', 'images/IUX6R26.png')
|
||||
oneShotFile('!shrek', 'Is love and is life.', 'Donkey!', 'images/QwuCQZA.png')
|
||||
oneShotFile('!sugondese', 'I don\'t like you.', 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr', 'images/VCvfvdz.png')
|
||||
oneShot('!bofa', 'bofa deez yes yes we get it', ':goorab:')
|
||||
oneShotFile('!squeedward', 'Who lives in an easter island head, under the sea?', 'Squeedward', 'images/squeedward.png')
|
||||
// oneShot('!imaginedragons', 'The worst part about any group of white people is that they could be Imagine Dragons and you\'d have no way of knowing.', '<https://i.imgur.com/QwuCQZA.png|Donkey!>')
|
||||
|
||||
command(
|
||||
|
@ -1778,9 +1935,9 @@ command(
|
|||
|
||||
command(
|
||||
['!addnft'],
|
||||
'Arguments 1 and 2 should be on the first line as name (one word!) and integer price.\n' +
|
||||
' The second line should be the description of the pieces\n' +
|
||||
' the picture is everything after the second line',
|
||||
`Arguments 1 and 2 should be on the first line as name (one word!) and integer price.
|
||||
The second line should be the description of the pieces
|
||||
the picture is everything after the second line`,
|
||||
async ({ event }) => {
|
||||
let [, name, ...price] = event.text.substring(0, event.text.indexOf('\n')).split(' ')
|
||||
const rest = event.text.substring(event.text.indexOf('\n') + 1)
|
||||
|
@ -1795,7 +1952,6 @@ command(
|
|||
owner: null
|
||||
}
|
||||
nfts.push(newNft)
|
||||
console.log('addedNft', newNft)
|
||||
}, adminOnly)
|
||||
|
||||
command(
|
||||
|
@ -1852,7 +2008,7 @@ command(
|
|||
async ({ args, say }) => {
|
||||
const target = args[0]
|
||||
const targetId = idFromWord(target)
|
||||
const targetUser = getUser(targetId)
|
||||
const targetUser = await getUser(targetId)
|
||||
await say(
|
||||
`${target} is currently earning \`${commas(getCPS(targetUser))}\` HVAC Coin per second.\n\n` +
|
||||
`They have ${commas(getCoins(targetId))} HVAC Coins\n\n` +
|
||||
|
@ -1871,17 +2027,14 @@ command(
|
|||
return
|
||||
}
|
||||
let targetId = idFromWord(target)
|
||||
console.log({ user: event.user, target, targetId })
|
||||
if (event.user === targetId) {
|
||||
return say('What, are you trying to steal from yourself? What, are you stupid?')
|
||||
}
|
||||
if (!targetId) {
|
||||
targetId = slack.users.Admin
|
||||
}
|
||||
if (user.coins < amount) {
|
||||
return
|
||||
}
|
||||
const targetUser = getUser(targetId)
|
||||
const targetUser = await getUser(targetId)
|
||||
user.coins -= amount
|
||||
targetUser.coins += amount
|
||||
await say(`Stealing is wrong. Gave ${commas(amount)} of your HVAC to ${slack.users[targetId]}`)
|
||||
|
@ -1920,7 +2073,7 @@ command(
|
|||
['!giveach'],
|
||||
'!giveach @player ach_name',
|
||||
async ({ args, say, }) => {
|
||||
addAchievement(getUser(idFromWord(args[0])), args[1], say)
|
||||
addAchievement(await getUser(idFromWord(args[0])), args[1], say)
|
||||
//saveGame()
|
||||
}, adminOnly)
|
||||
|
||||
|
@ -1946,13 +2099,11 @@ command(
|
|||
args = rest
|
||||
}
|
||||
|
||||
console.log({args, channel})
|
||||
const target = idFromWord(args[0])
|
||||
const [, ...rest] = args
|
||||
const userInfo = await slack.app.client.users.info({
|
||||
user: target
|
||||
})
|
||||
console.log(userInfo)
|
||||
return slack.app.client.chat.postMessage({
|
||||
channel,
|
||||
text: rest.join(' '),
|
||||
|
@ -2059,12 +2210,8 @@ const updateStonkPrices = () => {
|
|||
|
||||
// TODO: Gotta take into account wrapping around to the end of the year
|
||||
Object.entries(stonkMarket.stonks).forEach(([, stonk]) => {
|
||||
console.log(stonk.pattern)
|
||||
console.log('try set')
|
||||
for (let i = stonkMarket.lastDay; i < today; i++) {
|
||||
console.log('set lastPrice')
|
||||
stonk.lastPrice = stonk.price
|
||||
console.log(stonk.pattern, stonkPatterns)
|
||||
stonk.price *= 1 + ((stonkPatterns[stonk.pattern] || stonkPatterns.duk)[stonk.index] / 100)
|
||||
stonk.index++
|
||||
if (stonk.index >= stonkPatterns[stonk.pattern]?.length) {
|
||||
|
@ -2202,7 +2349,7 @@ command(
|
|||
command(
|
||||
['!userjson'],
|
||||
'Fetch the raw JSON data for your user',
|
||||
async ({ user, trueSay }) => trueSay(JSON.stringify(user)),
|
||||
async ({ user, trueSay }) => trueSay('```\n' + JSON.stringify(user) + '\n```'),
|
||||
{ hidden: true }
|
||||
)
|
||||
|
||||
|
@ -2261,6 +2408,7 @@ command(
|
|||
)
|
||||
|
||||
webapi.launch()
|
||||
logMemoryUsage()
|
||||
|
||||
module.exports = {
|
||||
command,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
const { addAchievement, saveGame, getUser } = require('./utils')
|
||||
const { addAchievement, saveGame, getUser, saveUser } = require('./utils')
|
||||
const slack = require('../../slack')
|
||||
|
||||
let loreCount = 0
|
||||
const l = (text, {correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
|
||||
const l = (text, {file, correctReactions, correctResponse, incorrectResponse, jumpTo} = {}) => {
|
||||
loreCount += 1
|
||||
return {
|
||||
text,
|
||||
file,
|
||||
correctReactions,
|
||||
correctResponse,
|
||||
incorrectResponse,
|
||||
|
@ -37,13 +38,12 @@ const lore = [
|
|||
l(`The seventh was Small Bob.`),
|
||||
l(`The eighth was Maurice, who had quite a few words to say about the French, and was eventually elected president.`),
|
||||
l(`And the ninth...`),
|
||||
l(`Well, the ninth might actually amount to something.`),
|
||||
l(`https://i.imgur.com/eFreg7Y.gif\n`),
|
||||
l(`Well, the ninth might actually amount to something.`, {file: 'https://i.imgur.com/eFreg7Y.gif'})
|
||||
]
|
||||
|
||||
slack.onReaction(async ({ event, say }) => {
|
||||
try {
|
||||
const user = getUser(event.user)
|
||||
const user = await getUser(event.user)
|
||||
const item = await slack.getMessage({ channel: event.item.channel, ts: event.item.ts })
|
||||
const message = item.messages[0].text
|
||||
const loreData = slack.decodeData('lore', message)
|
||||
|
@ -59,7 +59,7 @@ slack.onReaction(async ({ event, say }) => {
|
|||
console.log('lore:', lore[user.lore])
|
||||
await say(lore[user.lore].correctResponse)
|
||||
user.lore += 1
|
||||
saveGame(`updating ${user.name}'s lore counter`)
|
||||
saveUser(event.user, user, 'updating lore counter')
|
||||
} catch (e) {console.error('onReaction error', e)}
|
||||
})
|
||||
|
||||
|
@ -77,7 +77,7 @@ const loreMessage = (user, say) => {
|
|||
return `Sorry. I'd love to tell you more, but I'm tired. Please check back later.`
|
||||
}
|
||||
|
||||
const loreRoute = async ({ say, args, user, isAdmin }) => {
|
||||
const loreRoute = async ({ say, args, user, isAdmin, event }) => {
|
||||
user.lore ??= 0
|
||||
if (!args[0]) {
|
||||
const message = loreMessage(user, say)
|
||||
|
@ -92,6 +92,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
|
|||
if (args[0] === 'reset') {
|
||||
user.lore = 0
|
||||
//saveGame()
|
||||
await saveUser(event.user, user, 'lore reset')
|
||||
return say(`I have reset your place in the story.`)
|
||||
}
|
||||
if (isAdmin) {
|
||||
|
@ -105,6 +106,7 @@ const loreRoute = async ({ say, args, user, isAdmin }) => {
|
|||
const jumpTo = parseInt(args[0])
|
||||
if (!isNaN(jumpTo)) {
|
||||
user.lore = jumpTo
|
||||
await saveUser(event.user, user, 'lore jump')
|
||||
//saveGame()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade, getUser } = require('./utils')
|
||||
const { commas, quackGradeMultiplier, prestigeMultiplier, makeBackup, userHasCheckedQuackgrade, getUser, saveUser } = require('./utils')
|
||||
const { quackStore } = require('./quackstore')
|
||||
const buyableItems = require('./buyableItems')
|
||||
const slack = require('../../slack')
|
||||
|
@ -77,6 +77,7 @@ const prestigeConfirmRoute = async ({ event, say, user, YEET }) => {
|
|||
|
||||
await say(prestigeMenu(user))
|
||||
await say(`Say !quack _upgrade-name_ to purchase new quackgrades!`)
|
||||
await saveUser(event.user, user, 'prestiging')
|
||||
//await say('You prestiged! Check out !quackstore to see what you can buy!')
|
||||
}
|
||||
|
||||
|
@ -109,7 +110,7 @@ const quackStoreText = user =>
|
|||
`\n\nYou have ${user.quacks ??= 0} quacks to spend.` +
|
||||
`\nQuackStore upgrades are currently boosting your CPS by ${commas((quackGradeMultiplier(user) - 1) * 100)}%`
|
||||
|
||||
const quackStoreRoute = async ({ user, say, args, YEET }) => {
|
||||
const quackStoreRoute = async ({ user, say, args, YEET, event }) => {
|
||||
user.quackUpgrades ??= {}
|
||||
if (!args[0] || !YEET) {
|
||||
await say(quackStoreText(user))
|
||||
|
@ -131,7 +132,9 @@ const quackStoreRoute = async ({ user, say, args, YEET }) => {
|
|||
if (quackItem.type === 'starter') {
|
||||
quackItem.effect(user)
|
||||
}
|
||||
|
||||
await say(`You bought ${args[0]}!`)
|
||||
await saveUser(event.user, user, 'quackgrade purchased')
|
||||
}
|
||||
|
||||
const buyQuackGradeButton = quackgrade => {
|
||||
|
@ -237,7 +240,7 @@ const buyQuackGrade = async ({ body, ack, say, trueSay, payload }) => {
|
|||
const buying = payload.action_id.substring('buy-quackgrade-'.length)
|
||||
|
||||
console.log(`buyQuackGrade ${buying} clicked`)
|
||||
const user = getUser(body.user.id)
|
||||
const user = await getUser(body.user.id)
|
||||
// if (!user.isPrestiging) {
|
||||
// console.log('You must be prestiging!')
|
||||
// return say(`You must be prestiging to use this menu!`)
|
||||
|
@ -262,7 +265,7 @@ Object.keys(quackStore).forEach(itemName => slack.app.action('buy-quackgrade-' +
|
|||
slack.app.action('complete_prestige', async ({ body, ack, say }) => {
|
||||
await ack()
|
||||
|
||||
const user = getUser(body.user.id)
|
||||
const user = await getUser(body.user.id)
|
||||
delete user.isPrestiging
|
||||
|
||||
await slack.app.client.chat.delete({
|
||||
|
@ -271,6 +274,7 @@ slack.app.action('complete_prestige', async ({ body, ack, say }) => {
|
|||
})
|
||||
|
||||
await say(`Prestige complete!`)
|
||||
await saveUser(body.user.id, user, 'prestige complete')
|
||||
})
|
||||
|
||||
const prestigeMenuRoute = async ({ say, user }) => {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
const { Pool } = require('pg')
|
||||
const fs = require('fs')
|
||||
//const jokes = require('../jokes')
|
||||
const config = require('../../config')
|
||||
const achievements = require('./achievements')
|
||||
const buyableItems = require('./buyableItems')
|
||||
const { quackStore, getChaos } = require('./quackstore')
|
||||
const slack = require("../../slack");
|
||||
|
||||
const dbPool = new Pool(config.postgres)
|
||||
|
||||
let jokes
|
||||
let slackUsers
|
||||
const setSlackUsers = users => {
|
||||
slackUsers = users
|
||||
|
@ -59,24 +62,58 @@ const parseOr = (parseable, fallback) => {
|
|||
}
|
||||
}
|
||||
|
||||
const makeBackup = () => {
|
||||
const fileName = saveDir + 'backups/' + saveFile + new Date().toLocaleString().replace(/[^a-z0-9]/gi, '_')
|
||||
let lastBackupTs = 0
|
||||
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}`)
|
||||
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
|
||||
const saveGame = (after, force = true) => {
|
||||
const saveGame = (after, force = true, skipLog = false) => {
|
||||
if (saves % 20 === 0) {
|
||||
makeBackup()
|
||||
saveAllUsers().catch(console.error)
|
||||
}
|
||||
saves += 1
|
||||
if (force || saves % 10 === 0) {
|
||||
if (!skipLog) {
|
||||
if (after) {
|
||||
console.log(`SAVING GAME after ${after}`)
|
||||
} else {
|
||||
console.log('SAVING GAME')
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(saveDir + saveFile, JSON.stringify(game, null, 2))
|
||||
}
|
||||
|
@ -187,6 +224,9 @@ const parseAll = (str, allNum, user) => {
|
|||
if (str.match(/^\d+$/)) {
|
||||
return parseInt(str)
|
||||
}
|
||||
if (allNum && str.match(/^some$/)) {
|
||||
return Math.floor(Math.random() * allNum)
|
||||
}
|
||||
if (allNum && str.match(/^\d+%$/)) {
|
||||
const percent = parseFloat(str) / 100
|
||||
if (percent > 1 || percent < 0) {
|
||||
|
@ -218,7 +258,19 @@ const calculateCost = ({ itemName, user, quantity = 1 }) => {
|
|||
}
|
||||
|
||||
const game = loadGame()
|
||||
const { users, nfts, squad } = game
|
||||
let { users, nfts, squad } = game
|
||||
|
||||
const getAllUsers = async () => {
|
||||
const result = await dbPool.query(`
|
||||
SELECT slack_id, data
|
||||
FROM hvacker_user
|
||||
`).catch(console.error)
|
||||
return Object.fromEntries(result.rows.map(
|
||||
({ slack_id: slackId, data }) => [slackId, data]))
|
||||
}
|
||||
// getAllUsers().then(collection => {
|
||||
// game.users = (users = collection)
|
||||
// })
|
||||
|
||||
const setHighestCoins = userId => {
|
||||
const prevMax = users[userId].highestEver || 0
|
||||
|
@ -254,7 +306,24 @@ const getIdFromName = name => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const getUser = (userId, updateCoins = false) => {
|
||||
const fetchUser = async (userId, updateCoins = false) => {
|
||||
const result = await dbPool.query(`
|
||||
SELECT data
|
||||
FROM hvacker_user
|
||||
WHERE slack_id = $1`
|
||||
, [userId])
|
||||
.catch(console.error)
|
||||
return result.rows[0]?.data
|
||||
}
|
||||
|
||||
const getUser = async (userId, updateCoins = false) => {
|
||||
// users[userId] = await fetchUser(userId)
|
||||
// console.log('USER', users[userId])
|
||||
//users[userId] = await fetchUser(userId)
|
||||
return getUserSync(userId, updateCoins)
|
||||
}
|
||||
|
||||
const getUserSync = (userId, updateCoins = false) => {
|
||||
users[userId] ??= {}
|
||||
users[userId].coins ??= 0
|
||||
users[userId].items ??= {}
|
||||
|
@ -265,8 +334,9 @@ const getUser = (userId, updateCoins = false) => {
|
|||
users[userId].startDate ??= new Date()
|
||||
// users[userId].name ??= slack.users[userId]
|
||||
if (updateCoins) {
|
||||
users[userId].coins = getCoins(userId)
|
||||
users[userId].coins = getCoins(userId, users[userId])
|
||||
}
|
||||
saveGame('getUserSync()', true, true)
|
||||
return users[userId]
|
||||
}
|
||||
|
||||
|
@ -277,8 +347,8 @@ const addCoins = (user, add) => {
|
|||
user.coins = Math.floor(user.coins)
|
||||
}
|
||||
|
||||
const getCoins = userId => {
|
||||
const user = getUser(userId)
|
||||
const getCoins = (userId, user) => {
|
||||
user = user || getUserSync(userId)
|
||||
const currentTime = getSeconds()
|
||||
const lastCheck = user.lastCheck || currentTime
|
||||
const secondsPassed = currentTime - lastCheck
|
||||
|
@ -424,7 +494,7 @@ const shufflePercent = (str, percentOdds) => {
|
|||
}
|
||||
|
||||
const definitelyShuffle = (str, percentOdds) => {
|
||||
if (!str) {
|
||||
if (!str || str.length === 1) {
|
||||
return str
|
||||
}
|
||||
if (!percentOdds) {
|
||||
|
@ -578,8 +648,26 @@ const updateAll = async ({ name, text, blocks, add: { channel, ts } = {} }) => {
|
|||
// // return alreadyHas
|
||||
}
|
||||
|
||||
const logMemoryUsage = name => {
|
||||
const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`;
|
||||
|
||||
const memoryData = process.memoryUsage();
|
||||
const formattedData = {
|
||||
rss: `${formatMemoryUsage(memoryData.rss)} -> Resident Set Size - total memory allocated for the process execution`,
|
||||
// heapTotal: `${formatMemoryUsage(memoryData.heapTotal)} -> total size of the allocated heap`,
|
||||
// heapUsed: `${formatMemoryUsage(memoryData.heapUsed)} -> actual memory used during the execution`,
|
||||
// external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
|
||||
}
|
||||
if (name) {
|
||||
console.log(name, formattedData)
|
||||
} else {
|
||||
console.log(formattedData)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveGame,
|
||||
saveUser,
|
||||
makeBackup,
|
||||
logError,
|
||||
parseOr,
|
||||
|
@ -590,6 +678,7 @@ module.exports = {
|
|||
addAchievement,
|
||||
getCoins,
|
||||
getUser,
|
||||
getUserSync,
|
||||
singleItemCps,
|
||||
getCPS,
|
||||
getItemCps,
|
||||
|
@ -617,5 +706,7 @@ module.exports = {
|
|||
updateAll,
|
||||
setSlackAppClientChatUpdate: update => slackAppClientChatUpdate = update,
|
||||
setUpgrades,
|
||||
setSlackUsers
|
||||
setSlackUsers,
|
||||
setJokes: _jokes => jokes = _jokes,
|
||||
logMemoryUsage
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const express = require('express')
|
||||
const app = express()
|
||||
const port = 3001
|
||||
const port = process.env.HVACKER_API_PORT || 3001
|
||||
const crypto = require('crypto')
|
||||
const base64 = require('base-64')
|
||||
const slack = require('../../slack')
|
||||
const { game: { users }, getUser, fuzzyMatcher } = require('./utils')
|
||||
const { game: { users }, getUser, fuzzyMatcher, saveUser } = require('./utils')
|
||||
|
||||
const apiGetUserId = hash => {
|
||||
return Object.entries(users)
|
||||
|
@ -71,35 +71,21 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
|
|||
const encoded = req.header('Authorization').substring(5)
|
||||
const decoded = base64.decode(encoded).substring(1)
|
||||
const hash = makeHash(decoded)
|
||||
console.log({ hash })
|
||||
|
||||
const event = {
|
||||
user: apiGetUserId(hash)
|
||||
}
|
||||
const user = getUser(event.user)
|
||||
const user = await getUser(event.user)
|
||||
if (user.name !== 'TEST-USER' && illegalCommands.includes(commandName?.toLowerCase())) {
|
||||
res.send('Command is illegal over the web api!')
|
||||
return
|
||||
}
|
||||
if (!event.user) {
|
||||
res.status(400)
|
||||
res.send(
|
||||
'User with that password does not exist, or user does not have a password.\n' +
|
||||
'See \'!setpw help\' for assistance.\n'
|
||||
)
|
||||
console.log(' bad password')
|
||||
res.send(`User with that password does not exist, or user does not have a password.
|
||||
See '!setpw help' for assistance.\n`)
|
||||
return
|
||||
}
|
||||
// const lastCall = lastCalls[event.user] || 0
|
||||
// const secondsBetweenCalls = 2
|
||||
// const currentTime = Math.floor(new Date().getTime() / 1000)
|
||||
// if (lastCall + secondsBetweenCalls > currentTime) {
|
||||
// res.status(400)
|
||||
// res.send(`Must have at least ${secondsBetweenCalls}s between api calls`)
|
||||
// console.log(' rate limited')
|
||||
// return
|
||||
// }
|
||||
// console.log(` went through for ${slack.users[event.user]}`)
|
||||
// lastCalls[event.user] = currentTime
|
||||
|
||||
if (words[1] === 'help') {
|
||||
await say(commandNames.map(name => `\`${name}\``).join(', ') + ': ' + helpText)
|
||||
|
@ -109,19 +95,19 @@ const addCommand = ({ commandNames, helpText, action, condition, hidden }) => {
|
|||
return
|
||||
}
|
||||
const haunted = false
|
||||
//await action({ event, say, words, args, commandName })
|
||||
const canUse = await condition({ event, say, words, commandName, args, user, userId: event.user, isAdmin: event.user.includes(slack.users.Admin) })
|
||||
if (!canUse) {
|
||||
await say(`Command '${words[0]}' not found`)
|
||||
return
|
||||
}
|
||||
await action({ event, say, trueSay: say, words, args, commandName, user, userId: event.user, haunted })
|
||||
await saveUser(event.user, user, 'webapi action')
|
||||
} catch (e) {
|
||||
console.error('route error', e)
|
||||
const example = "`curl --location-trusted -u ':your-pw' quacker.sagev.space/lb`"
|
||||
await say(`Routing error. Make sure you've set up API access with the !setpw command in slack!\n` +
|
||||
`Then you can use calls like ${example}\n` +
|
||||
`N.b. --location-trusted is needed because quacker.sagev.space is technically a redirect, and your headers need to be forwarded.`)
|
||||
await say(`Routing error. Make sure you've set up API access with the !setpw command in slack!
|
||||
Then you can use calls like ${example}
|
||||
N.b. --location-trusted is needed because quacker.sagev.space is technically a redirect, and your headers need to be forwarded.`)
|
||||
}
|
||||
}
|
||||
commandNames.forEach(name => name !== '!!help' &&
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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
|
||||
const jokes = [
|
||||
|
@ -106,3 +107,5 @@ module.exports = {
|
|||
tellJoke,
|
||||
newsAlert
|
||||
}
|
||||
|
||||
setJokes(module.exports)
|
|
@ -1,6 +1,7 @@
|
|||
const { App: SlackApp } = require('@slack/bolt')
|
||||
const config = require('../config')
|
||||
const fs = require('fs')
|
||||
const http = require('http')
|
||||
const { addReactions, saveGame, setSlackAppClientChatUpdate, parseOr, setSlackUsers } = require('../games/hvacoins/utils')
|
||||
|
||||
const temperatureChannelId = 'C034156CE03'
|
||||
|
@ -72,7 +73,7 @@ const buildSayPrepend = ({ say, prepend }) => async msg => {
|
|||
})
|
||||
}
|
||||
|
||||
process.once('SIGINT', code => {
|
||||
process.once('SIGINT', () => {
|
||||
saveGame('SIGINT', true)
|
||||
process.exit()
|
||||
})
|
||||
|
@ -82,6 +83,9 @@ const activePolls = {}
|
|||
const testId = 'U028BMEBWBV_TEST'
|
||||
let testMode = false
|
||||
app.event('message', async ({ event, context, client, say }) => {
|
||||
if (event.text?.startsWith('!!!')) {
|
||||
return
|
||||
}
|
||||
if (event.subtype !== 'message_changed' && event?.text !== '!') {
|
||||
console.log('message.event', {
|
||||
...event,
|
||||
|
@ -89,6 +93,16 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
})
|
||||
}
|
||||
if (event?.user === users.Admin) {
|
||||
if (event.files?.length) {
|
||||
const fileName = Math.random().toString()
|
||||
const file = fs.createWriteStream(fileName)
|
||||
await app.client.files.upload({
|
||||
channels: event.channel,
|
||||
initial_comment: `Here's your file!`,
|
||||
file: createReadStream(path)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (event?.text.startsWith('!')) {
|
||||
if (testMode) {
|
||||
await messageAdmin('Currently in test mode!')
|
||||
|
@ -108,9 +122,9 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
for (const listener of messageListeners) {
|
||||
listener({ event, say })
|
||||
}
|
||||
if (event.user) {
|
||||
console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
||||
}
|
||||
// if (event.user) {
|
||||
// console.log('MSG', users[event.user], "'" + event.text + "'", new Date().toLocaleTimeString())
|
||||
// }
|
||||
if (event.user === users.Admin && event.channel === 'D0347Q4H9FE') {
|
||||
if (event.text === '!!kill') {
|
||||
saveGame('!!kill', true)
|
||||
|
@ -184,7 +198,9 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
}))
|
||||
|
||||
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)
|
||||
votes = votes.filter(v => [goodEmoji, hotterEmoji, colderEmoji].find(emoji => v.startsWith(emoji)))
|
||||
if (votes.length === 1) {
|
||||
|
@ -215,11 +231,19 @@ app.event('message', async ({ event, context, client, say }) => {
|
|||
|
||||
let text
|
||||
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 += 'raise the temperature, quack.'
|
||||
requestTempChange('Hotter')
|
||||
} 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 += 'lower the temperature, quack quack.'
|
||||
requestTempChange('Colder')
|
||||
} else {
|
||||
|
|