2018-05-03 12:55:21 -04:00
|
|
|
const fs = require('fs');
|
2017-12-24 15:04:08 -05:00
|
|
|
const path = require('path');
|
2020-08-12 16:18:42 -04:00
|
|
|
const Ajv = require('ajv');
|
|
|
|
const schema = require('./data/cfg.schema.json');
|
2017-12-24 15:04:08 -05:00
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
/** @type {ModmailConfig} */
|
|
|
|
let config = {};
|
2017-09-19 13:23:55 -04:00
|
|
|
|
2019-12-02 12:01:36 -05:00
|
|
|
// Config files to search for, in priority order
|
2018-05-03 12:55:21 -04:00
|
|
|
const configFiles = [
|
2019-12-02 18:51:11 -05:00
|
|
|
'config.ini',
|
2018-05-03 12:55:21 -04:00
|
|
|
'config.json',
|
|
|
|
'config.json5',
|
2020-08-12 16:18:42 -04:00
|
|
|
'config.js',
|
|
|
|
|
|
|
|
// Possible config files when file extensions are hidden
|
|
|
|
'config.ini.ini',
|
|
|
|
'config.ini.txt',
|
2018-05-03 12:55:21 -04:00
|
|
|
'config.json.json',
|
2018-08-07 18:32:22 -04:00
|
|
|
'config.json.txt',
|
2018-05-03 12:55:21 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
let foundConfigFile;
|
|
|
|
for (const configFile of configFiles) {
|
|
|
|
try {
|
|
|
|
fs.accessSync(__dirname + '/../' + configFile);
|
|
|
|
foundConfigFile = configFile;
|
|
|
|
break;
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
// Load config values from a config file (if any)
|
2019-12-02 19:27:55 -05:00
|
|
|
if (foundConfigFile) {
|
|
|
|
console.log(`Loading configuration from ${foundConfigFile}...`);
|
|
|
|
try {
|
|
|
|
if (foundConfigFile.endsWith('.js')) {
|
2020-08-12 16:18:42 -04:00
|
|
|
config = require(`../${foundConfigFile}`);
|
2019-12-02 12:01:36 -05:00
|
|
|
} else {
|
2019-12-02 19:27:55 -05:00
|
|
|
const raw = fs.readFileSync(__dirname + '/../' + foundConfigFile, {encoding: "utf8"});
|
|
|
|
if (foundConfigFile.endsWith('.ini') || foundConfigFile.endsWith('.ini.txt')) {
|
2020-08-12 16:18:42 -04:00
|
|
|
config = require('ini').decode(raw);
|
2019-12-02 19:27:55 -05:00
|
|
|
} else {
|
2020-08-12 16:18:42 -04:00
|
|
|
config = require('json5').parse(raw);
|
2019-12-02 19:27:55 -05:00
|
|
|
}
|
2019-12-02 12:01:36 -05:00
|
|
|
}
|
2019-12-02 19:27:55 -05:00
|
|
|
} catch (e) {
|
|
|
|
throw new Error(`Error reading config file! The error given was: ${e.message}`);
|
2018-08-07 18:32:22 -04:00
|
|
|
}
|
2017-09-19 13:23:55 -04:00
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
// Set dynamic default values which can't be set in the schema directly
|
|
|
|
config.dbDir = path.join(__dirname, '..', 'db');
|
|
|
|
config.logDir = path.join(__dirname, '..', 'logs'); // Only used for migrating data from older Modmail versions
|
2017-09-19 13:23:55 -04:00
|
|
|
|
2019-12-02 19:27:55 -05:00
|
|
|
// Load config values from environment variables
|
|
|
|
const envKeyPrefix = 'MM_';
|
|
|
|
let loadedEnvValues = 0;
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(process.env)) {
|
|
|
|
if (! key.startsWith(envKeyPrefix)) continue;
|
|
|
|
|
|
|
|
// MM_CLOSE_MESSAGE -> closeMessage
|
|
|
|
// MM_COMMAND_ALIASES__MV => commandAliases.mv
|
|
|
|
const configKey = key.slice(envKeyPrefix.length)
|
|
|
|
.toLowerCase()
|
|
|
|
.replace(/([a-z])_([a-z])/g, (m, m1, m2) => `${m1}${m2.toUpperCase()}`)
|
|
|
|
.replace('__', '.');
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
config[configKey] = value.includes('||')
|
2019-12-02 19:40:50 -05:00
|
|
|
? value.split('||')
|
|
|
|
: value;
|
2019-12-02 19:27:55 -05:00
|
|
|
|
2019-12-02 19:40:50 -05:00
|
|
|
loadedEnvValues++;
|
2019-12-02 19:27:55 -05:00
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
if (process.env.PORT && ! process.env.MM_PORT) {
|
2019-12-02 19:27:55 -05:00
|
|
|
// Special case: allow common "PORT" environment variable without prefix
|
2020-08-12 16:18:42 -04:00
|
|
|
config.port = process.env.PORT;
|
2019-12-02 19:40:50 -05:00
|
|
|
loadedEnvValues++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loadedEnvValues > 0) {
|
|
|
|
console.log(`Loaded ${loadedEnvValues} ${loadedEnvValues === 1 ? 'value' : 'values'} from environment variables`);
|
2019-12-02 19:27:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert config keys with periods to objects
|
|
|
|
// E.g. commandAliases.mv -> commandAliases: { mv: ... }
|
2020-08-12 16:18:42 -04:00
|
|
|
for (const [key, value] of Object.entries(config)) {
|
2019-12-02 19:27:55 -05:00
|
|
|
if (! key.includes('.')) continue;
|
|
|
|
|
|
|
|
const keys = key.split('.');
|
2020-08-12 16:18:42 -04:00
|
|
|
let cursor = config;
|
2019-12-02 19:27:55 -05:00
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
if (i === keys.length - 1) {
|
|
|
|
cursor[keys[i]] = value;
|
|
|
|
} else {
|
|
|
|
cursor[keys[i]] = cursor[keys[i]] || {};
|
|
|
|
cursor = cursor[keys[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
delete config[key];
|
2019-12-02 19:27:55 -05:00
|
|
|
}
|
2017-12-31 19:16:05 -05:00
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
// Cast boolean options (on, true, 1) (off, false, 0)
|
|
|
|
for (const [key, value] of Object.entries(config)) {
|
|
|
|
if (typeof value !== "string") continue;
|
|
|
|
if (["on", "true", "1"].includes(value)) {
|
|
|
|
config[key] = true;
|
|
|
|
} else if (["off", "false", "0"].includes(value)) {
|
|
|
|
config[key] = false;
|
2017-09-19 13:23:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
if (! config['knex']) {
|
|
|
|
config.knex = {
|
2017-12-24 15:04:08 -05:00
|
|
|
client: 'sqlite',
|
2020-08-12 16:18:42 -04:00
|
|
|
connection: {
|
|
|
|
filename: path.join(config.dbDir, 'data.sqlite')
|
2017-12-24 15:04:08 -05:00
|
|
|
},
|
|
|
|
useNullAsDefault: true
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-04-21 08:38:21 -04:00
|
|
|
// Make sure migration settings are always present in knex config
|
2020-08-12 16:18:42 -04:00
|
|
|
Object.assign(config['knex'], {
|
2017-12-24 15:04:08 -05:00
|
|
|
migrations: {
|
2020-08-12 16:18:42 -04:00
|
|
|
directory: path.join(config.dbDir, 'migrations')
|
2017-12-24 15:04:08 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-04-21 08:38:21 -04:00
|
|
|
// Make sure mainGuildId is internally always an array
|
2020-08-12 16:18:42 -04:00
|
|
|
if (! Array.isArray(config['mainGuildId'])) {
|
|
|
|
config['mainGuildId'] = [config['mainGuildId']];
|
2018-04-21 08:38:21 -04:00
|
|
|
}
|
|
|
|
|
2018-09-20 15:27:59 -04:00
|
|
|
// Make sure inboxServerPermission is always an array
|
2020-08-12 16:18:42 -04:00
|
|
|
if (! Array.isArray(config['inboxServerPermission'])) {
|
|
|
|
if (config['inboxServerPermission'] == null) {
|
|
|
|
config['inboxServerPermission'] = [];
|
2018-09-20 15:27:59 -04:00
|
|
|
} else {
|
2020-08-12 16:18:42 -04:00
|
|
|
config['inboxServerPermission'] = [config['inboxServerPermission']];
|
2018-09-20 15:27:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-09 08:56:04 -04:00
|
|
|
// Move greetingMessage/greetingAttachment to the guildGreetings object internally
|
|
|
|
// Or, in other words, if greetingMessage and/or greetingAttachment is set, it is applied for all servers that don't
|
|
|
|
// already have something set up in guildGreetings. This retains backwards compatibility while allowing you to override
|
|
|
|
// greetings for specific servers in guildGreetings.
|
2020-08-12 16:18:42 -04:00
|
|
|
if (config.greetingMessage || config.greetingAttachment) {
|
|
|
|
for (const guildId of config.mainGuildId) {
|
|
|
|
if (config.guildGreetings[guildId]) continue;
|
|
|
|
config.guildGreetings[guildId] = {
|
|
|
|
message: config.greetingMessage,
|
|
|
|
attachment: config.greetingAttachment
|
2019-06-09 08:56:04 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-27 22:54:12 -04:00
|
|
|
// newThreadCategoryId is syntactic sugar for categoryAutomation.newThread
|
2020-08-12 16:18:42 -04:00
|
|
|
if (config.newThreadCategoryId) {
|
|
|
|
config.categoryAutomation.newThread = config.newThreadCategoryId;
|
|
|
|
delete config.newThreadCategoryId;
|
2019-03-27 22:54:12 -04:00
|
|
|
}
|
|
|
|
|
2019-12-02 19:00:00 -05:00
|
|
|
// Turn empty string options to null (i.e. "option=" without a value in config.ini)
|
2020-08-12 16:18:42 -04:00
|
|
|
for (const [key, value] of Object.entries(config)) {
|
2019-12-02 19:00:00 -05:00
|
|
|
if (value === '') {
|
2020-08-12 16:18:42 -04:00
|
|
|
config[key] = null;
|
2019-12-02 19:00:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
// Validate config and assign defaults (if missing)
|
2020-08-12 16:24:17 -04:00
|
|
|
const ajv = new Ajv({ useDefaults: true, coerceTypes: "array" });
|
2020-08-12 16:18:42 -04:00
|
|
|
const validate = ajv.compile(schema);
|
|
|
|
const configIsValid = validate(config);
|
2019-12-02 19:00:00 -05:00
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
if (! configIsValid) {
|
|
|
|
console.error('Issues with configuration options:');
|
|
|
|
for (const error of validate.errors) {
|
|
|
|
console.error(`The "${error.dataPath.slice(1)}" option ${error.message}`);
|
2019-12-02 19:00:00 -05:00
|
|
|
}
|
2020-08-12 16:18:42 -04:00
|
|
|
console.error('');
|
|
|
|
console.error('Please restart the bot after fixing the issues mentioned above.');
|
|
|
|
process.exit(1);
|
2019-12-02 19:00:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Configuration ok!");
|
2019-12-02 12:01:36 -05:00
|
|
|
|
2020-08-12 16:18:42 -04:00
|
|
|
module.exports = config;
|