diff --git a/.gitignore b/.gitignore index f47b2d2..c6d7b75 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ node_modules/ todo.txt *.log *.swp -dev.sqlite3 +*.sqlite3 dev-config.json diff --git a/docs/hosting.md b/docs/hosting.md index 42ca24a..40e8d85 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -9,6 +9,7 @@ This guide assumes you're hosting on a Linux distro with `systemd`. The bot will work on other platforms, but you're on your own figuring that out. ## Running as a user (in dev-mode) + Quick and easy. Also (mostly) platform-independent! Create a file `config.json` and paste in the following (obviously fill in the @@ -36,6 +37,7 @@ npm start path/to/your/config.json ``` ## Running as a service + A little more effort to set up, but better for long-term use. The provided service file expects to find the bot code at @@ -72,3 +74,20 @@ the service file a bit (see comments in provided service file). Now you should be able to run `systemctl restart reactionrolebot.service` to start your bot. + +## Additional config + +`config.json` supports a few more optional fields: + +- **database_file** - Use a different SQLite3 database file. This defaults to `/srv/discord/rolebot.sqlite3`. Users on non-Linux systems will probably want to change this. +- **enable_precache** - Makes the bot pre-cache all messages with reaction roles on startup. This can help the bot be more consistent when it first starts, but will cause larger bots to be rate limited. Use with caution! +- **guild_id** - Limits slash command registration to this Guild. This is useful for bot development. +- **log_file** - Use a different log file. This defaults to `output.log` in the project root. + +You can also run the bot with a config file other than `config.json`, but you must provide an absolute file path! + +Examples: +``` +npm run knex migrate:latest /absolute/path/to/my_config.json +npm start /absolute/config/path.json +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8046a39..9414886 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "reactionrolebot", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 2, "requires": true, "packages": { @@ -9,10 +9,11 @@ "version": "2.0.0", "license": "AGPL-3.0", "dependencies": { - "discord-command-registry": "^1.2.0", + "discord-command-registry": "^1.2.1", "discord.js": "^13.3.1", "knex": "^0.20.13", "lodash": "^4.17.20", + "minimist": "^1.2.6", "multimap": "^1.1.0", "node-cache": "^5.1.2", "sqlite3": "^4.2.0", @@ -38,61 +39,51 @@ } }, "node_modules/@discordjs/builders": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.9.0.tgz", - "integrity": "sha512-XM/5yrTxMF0SDKza32YzGDQO1t+qEJTaF8Zvxu/UOjzoqzMPPGQBjC1VgZxz8/CBLygW5qI+UVygMa88z13G3g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.13.0.tgz", + "integrity": "sha512-4L9y26KRNNU8Y7J78SRUN1Uhava9D8jfit/YqEaKi8gQRc7PdqKqk2poybo6RXaiyt/BgKYPfcjxT7WvzGfYCA==", "dependencies": { - "@sindresorhus/is": "^4.2.0", - "discord-api-types": "^0.24.0", - "ts-mixer": "^6.0.0", - "tslib": "^2.3.1", - "zod": "^3.11.6" + "@sapphire/shapeshift": "^2.0.0", + "@sindresorhus/is": "^4.6.0", + "discord-api-types": "^0.31.1", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.1", + "tslib": "^2.3.1" }, "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" + "node": ">=16.9.0" } }, - "node_modules/@discordjs/builders/node_modules/discord-api-types": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", - "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==", - "deprecated": "No longer supported. Install the latest release!", + "node_modules/@discordjs/collection": { + "version": "0.7.0-dev.1650586175-61a44c5", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0-dev.1650586175-61a44c5.tgz", + "integrity": "sha512-GyBtogCnQjh0AKdBFU+ScoCLbaQuUoOzA6e1+ckDe/7coOFEheOlt4wN03eolW6v/raAZpA1ozJZDGB7VHTDIA==", "engines": { - "node": ">=12" + "node": ">=16.9.0" } }, - "node_modules/@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, "node_modules/@discordjs/rest": { - "version": "0.1.0-canary.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.1.0-canary.0.tgz", - "integrity": "sha512-d+s//ISYVV+e0w/926wMEeO7vju+Pn11x1JM4tcmVMCHSDgpi6pnFCNAXF1TEdnDcy7xf9tq5cf2pQkb/7ySTQ==", - "dependencies": { - "@discordjs/collection": "^0.1.6", - "@sapphire/async-queue": "^1.1.4", - "@sapphire/snowflake": "^1.3.5", - "abort-controller": "^3.0.0", - "discord-api-types": "^0.18.1", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.4.1.tgz", + "integrity": "sha512-rtWy+AIfNlfjGkAgA2TJLASdqli07aTNQceVGT6RQQiQaEqV0nsfBO4WtDlDzk7PmO3w+InP3dpwEolJI5jz0A==", + "dependencies": { + "@discordjs/collection": "^0.7.0-dev", + "@sapphire/async-queue": "^1.3.1", + "@sapphire/snowflake": "^3.2.1", + "@types/node-fetch": "^2.6.1", + "discord-api-types": "^0.29.0", "form-data": "^4.0.0", - "node-fetch": "^2.6.1", - "tslib": "^2.3.0" + "node-fetch": "^2.6.7", + "tslib": "^2.3.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.18.1.tgz", - "integrity": "sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg==", - "deprecated": "No longer supported. Install the latest release (0.20.2)", - "engines": { - "node": ">=12" - } + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.29.0.tgz", + "integrity": "sha512-Ekq1ICNpOTVajXKZguNFrsDeTmam+ZeA38txsNLZnANdXUjU6QBPIZLUQTC6MzigFGb0Tt8vk4xLnXmzv0shNg==" }, "node_modules/@sapphire/async-queue": { "version": "1.3.1", @@ -103,14 +94,22 @@ "npm": ">=7.0.0" } }, + "node_modules/@sapphire/shapeshift": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-2.0.0.tgz", + "integrity": "sha512-SKq4mKZXW2xB6O2Im4lBCDYy8pnuAMC3Zziw5Ub7WvZMt0fgwebDnqh+9MhoMYUSjkLyQcEO8ZS9d3ES7aRhBw==", + "engines": { + "node": ">=v15.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sapphire/snowflake": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-1.3.6.tgz", - "integrity": "sha512-QnzuLp+p9D7agynVub/zqlDVriDza9y3STArBhNiNBUgIX8+GL5FpQxstRfw1jDr5jkZUjcuKYAHxjIuXKdJAg==", - "deprecated": "This version has been automatically deprecated by @favware/npm-deprecate. Please use a newer version.", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.1.tgz", + "integrity": "sha512-vmZq1I6J6iNRQVXP+N9HzOMOY4ORB3MunoFeWCw/aBnZTf1cDgDvP0RZFQS53B1TN95AIgFY9T+ItQ/fWAUYWQ==", "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, "node_modules/@sindresorhus/is": { @@ -164,17 +163,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -659,22 +647,18 @@ } }, "node_modules/discord-api-types": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz", - "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==", - "deprecated": "No longer supported. Install the latest release!", - "engines": { - "node": ">=12" - } + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.31.2.tgz", + "integrity": "sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA==" }, "node_modules/discord-command-registry": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.0.tgz", - "integrity": "sha512-HqZd4owQ/prvhEDI60TbD2YqOhJ4HLFjBGUr9zPHcO0Aihvphn6RfAAc1Xqzad9rbsxGOcaTYBRFOpMtklVwgw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.1.tgz", + "integrity": "sha512-XZganR2rI99yHwYTUzByDJj6oS37x642ZPLExfDOn0dq1e8ObUzpMxmJnrVAC5620Sf1C4NMFZVUSDtHIc//ZQ==", "dependencies": { - "@discordjs/builders": "^0.9.0", - "@discordjs/rest": "^0.1.0-canary.0", - "discord-api-types": "^0.25.2" + "@discordjs/builders": "^0.13.0", + "@discordjs/rest": "^0.4.1", + "discord-api-types": "^0.31.2" }, "peerDependencies": { "discord.js": "^13.1.0" @@ -746,14 +730,6 @@ "node": ">=6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -955,6 +931,11 @@ "node": ">=0.10.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -3090,48 +3071,42 @@ } }, "@discordjs/builders": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.9.0.tgz", - "integrity": "sha512-XM/5yrTxMF0SDKza32YzGDQO1t+qEJTaF8Zvxu/UOjzoqzMPPGQBjC1VgZxz8/CBLygW5qI+UVygMa88z13G3g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.13.0.tgz", + "integrity": "sha512-4L9y26KRNNU8Y7J78SRUN1Uhava9D8jfit/YqEaKi8gQRc7PdqKqk2poybo6RXaiyt/BgKYPfcjxT7WvzGfYCA==", "requires": { - "@sindresorhus/is": "^4.2.0", - "discord-api-types": "^0.24.0", - "ts-mixer": "^6.0.0", - "tslib": "^2.3.1", - "zod": "^3.11.6" - }, - "dependencies": { - "discord-api-types": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", - "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==" - } + "@sapphire/shapeshift": "^2.0.0", + "@sindresorhus/is": "^4.6.0", + "discord-api-types": "^0.31.1", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.1", + "tslib": "^2.3.1" } }, "@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + "version": "0.7.0-dev.1650586175-61a44c5", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0-dev.1650586175-61a44c5.tgz", + "integrity": "sha512-GyBtogCnQjh0AKdBFU+ScoCLbaQuUoOzA6e1+ckDe/7coOFEheOlt4wN03eolW6v/raAZpA1ozJZDGB7VHTDIA==" }, "@discordjs/rest": { - "version": "0.1.0-canary.0", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.1.0-canary.0.tgz", - "integrity": "sha512-d+s//ISYVV+e0w/926wMEeO7vju+Pn11x1JM4tcmVMCHSDgpi6pnFCNAXF1TEdnDcy7xf9tq5cf2pQkb/7ySTQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-0.4.1.tgz", + "integrity": "sha512-rtWy+AIfNlfjGkAgA2TJLASdqli07aTNQceVGT6RQQiQaEqV0nsfBO4WtDlDzk7PmO3w+InP3dpwEolJI5jz0A==", "requires": { - "@discordjs/collection": "^0.1.6", - "@sapphire/async-queue": "^1.1.4", - "@sapphire/snowflake": "^1.3.5", - "abort-controller": "^3.0.0", - "discord-api-types": "^0.18.1", + "@discordjs/collection": "^0.7.0-dev", + "@sapphire/async-queue": "^1.3.1", + "@sapphire/snowflake": "^3.2.1", + "@types/node-fetch": "^2.6.1", + "discord-api-types": "^0.29.0", "form-data": "^4.0.0", - "node-fetch": "^2.6.1", - "tslib": "^2.3.0" + "node-fetch": "^2.6.7", + "tslib": "^2.3.1" }, "dependencies": { "discord-api-types": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.18.1.tgz", - "integrity": "sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg==" + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.29.0.tgz", + "integrity": "sha512-Ekq1ICNpOTVajXKZguNFrsDeTmam+ZeA38txsNLZnANdXUjU6QBPIZLUQTC6MzigFGb0Tt8vk4xLnXmzv0shNg==" } } }, @@ -3140,10 +3115,15 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.1.tgz", "integrity": "sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==" }, + "@sapphire/shapeshift": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-2.0.0.tgz", + "integrity": "sha512-SKq4mKZXW2xB6O2Im4lBCDYy8pnuAMC3Zziw5Ub7WvZMt0fgwebDnqh+9MhoMYUSjkLyQcEO8ZS9d3ES7aRhBw==" + }, "@sapphire/snowflake": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-1.3.6.tgz", - "integrity": "sha512-QnzuLp+p9D7agynVub/zqlDVriDza9y3STArBhNiNBUgIX8+GL5FpQxstRfw1jDr5jkZUjcuKYAHxjIuXKdJAg==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.1.tgz", + "integrity": "sha512-vmZq1I6J6iNRQVXP+N9HzOMOY4ORB3MunoFeWCw/aBnZTf1cDgDvP0RZFQS53B1TN95AIgFY9T+ItQ/fWAUYWQ==" }, "@sindresorhus/is": { "version": "4.6.0", @@ -3189,14 +3169,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -3579,18 +3551,18 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "discord-api-types": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.25.2.tgz", - "integrity": "sha512-O243LXxb5gLLxubu5zgoppYQuolapGVWPw3ll0acN0+O8TnPUE2kFp9Bt3sTRYodw8xFIknOVxjSeyWYBpVcEQ==" + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.31.2.tgz", + "integrity": "sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA==" }, "discord-command-registry": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.0.tgz", - "integrity": "sha512-HqZd4owQ/prvhEDI60TbD2YqOhJ4HLFjBGUr9zPHcO0Aihvphn6RfAAc1Xqzad9rbsxGOcaTYBRFOpMtklVwgw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/discord-command-registry/-/discord-command-registry-1.2.1.tgz", + "integrity": "sha512-XZganR2rI99yHwYTUzByDJj6oS37x642ZPLExfDOn0dq1e8ObUzpMxmJnrVAC5620Sf1C4NMFZVUSDtHIc//ZQ==", "requires": { - "@discordjs/builders": "^0.9.0", - "@discordjs/rest": "^0.1.0-canary.0", - "discord-api-types": "^0.25.2" + "@discordjs/builders": "^0.13.0", + "@discordjs/rest": "^0.4.1", + "discord-api-types": "^0.31.2" } }, "discord.js": { @@ -3643,11 +3615,6 @@ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -3809,6 +3776,11 @@ } } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", diff --git a/package.json b/package.json index d028b90..f8677ea 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "reactionrolebot", - "version": "2.0.0", + "version": "2.0.1", "description": "I'm a basic, no BS Discord bot that can assign and unassign roles using message reactions. I am completely free and open source, and always will be.", "main": "src/main.js", "dependencies": { - "discord-command-registry": "^1.2.0", + "discord-command-registry": "^1.2.1", "discord.js": "^13.3.1", "knex": "^0.20.13", "lodash": "^4.17.20", + "minimist": "^1.2.6", "multimap": "^1.1.0", "node-cache": "^5.1.2", "sqlite3": "^4.2.0", diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..77b0472 --- /dev/null +++ b/src/config.js @@ -0,0 +1,55 @@ +/******************************************************************************* + * This file is part of No BS Role Reacts, a role-assigning Discord bot. + * Copyright (C) 2020 Mimickal (Mia Moretti). + * + * No BS Role Reacts is free software under the GNU Affero General Public + * License v3.0. See LICENSE or + * for more information. + ******************************************************************************/ +const fs = require('fs'); +const path = require('path'); + +const CONFIG_TEMPLATE = { + app_id: '', + token: '', +}; + +// This looks pretty jank, but really all we're doing here is trying to have +// sensible default config file locations. +// This isn't perfect, but it covers most use cases, including default dev and prod. +const DEFAULT_CONFIG_LOCATION = process.env.NODE_ENV === 'prod' + ? '/etc/discord/ReactionRoleBot/config.json' + : path.join(__dirname, '..', 'dev-config.json'); +const argv = require('minimist')(process.argv.slice(2)); +const conf_override = argv._.find(arg => arg.endsWith('json')); +let location = conf_override || DEFAULT_CONFIG_LOCATION; +let config; +try { + config = JSON.parse(fs.readFileSync(location)); +} catch (err) { + console.error(`Failed to read config file at ${location}`); + if (err.message.includes('no such file or directory')) { + console.error( + 'You can create the file there, or pass it in as a command ' + + 'line argument.\n' + + 'Example: npm run \n\n' + + 'If this if your first time running the bot, your config file ' + + 'must be a JSON file containing at least these fields:\n' + + JSON.stringify(CONFIG_TEMPLATE, null, 2) + '\n\n' + ); + } + console.error("Here's the full error:"); + console.error(err); + process.exit(1); +} + +// Enumerate the values here so intellisense (and maintainers) knows what's available. +module.exports = Object.seal({ + app_id: config.app_id, + database_file: config.database_file, + enable_precache: config.enable_precache, + guild_id: config.guild_id, + log_file: config.log_file, + token: config.token, +}); + diff --git a/src/events.js b/src/events.js index e70ad6d..16627c9 100644 --- a/src/events.js +++ b/src/events.js @@ -10,6 +10,7 @@ const lodash = require('lodash'); const Perms = require('discord.js').Permissions.FLAGS; const commands = require('./commands'); +const config = require('./config'); const database = require('./database'); const logger = require('./logger'); const { @@ -239,12 +240,29 @@ async function onReactionRemove(reaction, react_user) { * Event handler for when the bot is logged in. * * Logs the bot user we logged in as. - * - * Pre-caches messages we have react role mappings on. This prevents an issue - * where the bot sometimes fails to pick up reacts when it first restarts. */ async function onReady(client) { logger.info(`Logged in as ${client.user.tag} (${client.user.id})`); + + if (config.enable_precache) { + logger.warn(unindent(` + Precaching is VERY hard on Discord's API and will cause the bot to + get rate limited, unless the bot is only in very few servers. Use + with caution. + `)); + await precache(client); + } +} + +/** + * Pre-caches messages we have react role mappings on. This can prevent an issue + * where the bot sometimes fails to pick up reacts when it first restarts. + * + * WARNING: This function is *very* hard on Discord's API, and will cause the + * bot to get rate limited if it's in too many servers. This really shouldn't be + * used. + */ +async function precache(client) { logger.info('Precaching messages...'); // Despite message IDs being unique, we can only fetch a message by ID diff --git a/src/knexfile.js b/src/knexfile.js index 5556d75..014b5a8 100644 --- a/src/knexfile.js +++ b/src/knexfile.js @@ -8,12 +8,14 @@ ******************************************************************************/ const path = require('path'); +const config = require('./config'); + module.exports = { development: { client: 'sqlite3', useNullAsDefault: true, connection: { - filename: path.join(__dirname, '..', 'dev.sqlite3'), + filename: config.database_file || path.join(__dirname, '..', 'dev.sqlite3'), }, // Helps us catch hanging transactions in dev by locking up the database // if we forget to commit anything. @@ -35,7 +37,7 @@ module.exports = { client: 'sqlite3', useNullAsDefault: true, connection: { - filename: '/srv/discord/rolebot.sqlite3' + filename: config.database_file || '/srv/discord/rolebot.sqlite3', } } }; diff --git a/src/logger.js b/src/logger.js index 742f4f5..e669a1b 100644 --- a/src/logger.js +++ b/src/logger.js @@ -9,7 +9,9 @@ const path = require('path'); const Winston = require('winston'); -const LOG_FILE_NAME = path.join(__dirname, '..', 'output.log'); +const config = require('./config'); + +const LOG_FILE_NAME = config.log_file || path.join(__dirname, '..', 'output.log'); const IS_PROD = process.env.NODE_ENV === 'prod'; diff --git a/src/main.js b/src/main.js index 7c9bdf9..11acc51 100644 --- a/src/main.js +++ b/src/main.js @@ -6,16 +6,12 @@ * License v3.0. See LICENSE or * for more information. ******************************************************************************/ -const fs = require('fs'); - const Discord = require('discord.js'); +const config = require('./config'); const events = require('./events'); const logger = require('./logger'); -const CONFIG = JSON.parse(fs.readFileSync( - process.argv[2] || '/etc/discord/ReactionRoleBot/config.json' -)); const PACKAGE = require('../package.json'); // Everything operates on IDs, so we can safely rely on partials. @@ -23,7 +19,6 @@ const PACKAGE = require('../package.json'); const client = new Discord.Client({ intents: [ Discord.Intents.FLAGS.GUILDS, - Discord.Intents.FLAGS.GUILD_MEMBERS, Discord.Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS, Discord.Intents.FLAGS.GUILD_MESSAGES, Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS, @@ -54,7 +49,12 @@ client.on(Events.MESSAGE_REACTION_ADD, events.onReactionAdd); client.on(Events.MESSAGE_REACTION_REMOVE, events.onReactionRemove); -client.login(CONFIG.token).catch(err => { +logger.info(`Bot is starting with config: ${JSON.stringify({ + ...config, + token: '', +})}`); + +client.login(config.token).catch(err => { logger.error('Failed to log in!', err); process.exit(1); }); diff --git a/src/register.js b/src/register.js index 7e1926b..84b15a1 100644 --- a/src/register.js +++ b/src/register.js @@ -6,9 +6,9 @@ * License v3.0. See LICENSE or * for more information. ******************************************************************************/ -const fs = require('fs'); const path = require('path'); +const config = require('./config'); const logger = require('./logger'); const registry = require('./commands'); @@ -25,8 +25,6 @@ if (!process.argv[2]) { process.exit(); } -const config = JSON.parse(fs.readFileSync(process.argv[2])); - let output = 'Registering commmands '; if (config.guild_id) { output += `in guild ${config.guild_id}`; diff --git a/src/util.js b/src/util.js index 4ac3850..5e66270 100644 --- a/src/util.js +++ b/src/util.js @@ -119,7 +119,7 @@ function isDiscordId(str) { * Matches built-in unicode emoji literals. */ function isEmojiStr(str) { - return str?.match?.(/^\p{Extended_Pictographic}$/u); + return str?.match?.(/^\p{Emoji}+/u); } /**