Skip to content

Commit

Permalink
Merge pull request #62 from Mimickal/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Mimickal authored Apr 22, 2022
2 parents 0950358 + 936c74a commit c5bc194
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 156 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ node_modules/
todo.txt
*.log
*.swp
dev.sqlite3
*.sqlite3
dev-config.json
19 changes: 19 additions & 0 deletions docs/hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
```
244 changes: 108 additions & 136 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
55 changes: 55 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/agpl-3.0.en.html>
* for more information.
******************************************************************************/
const fs = require('fs');
const path = require('path');

const CONFIG_TEMPLATE = {
app_id: '<Your Discord bot application ID>',
token: '<Your Discord bot 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 <command> <path/to/your/config.json>\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,
});

24 changes: 21 additions & 3 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/knexfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -35,7 +37,7 @@ module.exports = {
client: 'sqlite3',
useNullAsDefault: true,
connection: {
filename: '/srv/discord/rolebot.sqlite3'
filename: config.database_file || '/srv/discord/rolebot.sqlite3',
}
}
};
Expand Down
4 changes: 3 additions & 1 deletion src/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';


Expand Down
14 changes: 7 additions & 7 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,19 @@
* License v3.0. See LICENSE or <https://www.gnu.org/licenses/agpl-3.0.en.html>
* 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.
// This allows reaction events to fire for uncached messages.
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,
Expand Down Expand Up @@ -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: '<REDACTED>',
})}`);

client.login(config.token).catch(err => {
logger.error('Failed to log in!', err);
process.exit(1);
});
Expand Down
4 changes: 1 addition & 3 deletions src/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* License v3.0. See LICENSE or <https://www.gnu.org/licenses/agpl-3.0.en.html>
* for more information.
******************************************************************************/
const fs = require('fs');
const path = require('path');

const config = require('./config');
const logger = require('./logger');
const registry = require('./commands');

Expand All @@ -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}`;
Expand Down
2 changes: 1 addition & 1 deletion src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down

0 comments on commit c5bc194

Please sign in to comment.