Fixes and tweaks to new config validation
parent
f7b8a312f9
commit
bd8dcc6129
85
src/cfg.js
85
src/cfg.js
|
@ -102,16 +102,6 @@ for (const [key, value] of Object.entries(config)) {
|
||||||
delete config[key];
|
delete config[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! config['knex']) {
|
if (! config['knex']) {
|
||||||
config.knex = {
|
config.knex = {
|
||||||
client: 'sqlite',
|
client: 'sqlite',
|
||||||
|
@ -129,20 +119,6 @@ Object.assign(config['knex'], {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure mainGuildId is internally always an array
|
|
||||||
if (! Array.isArray(config['mainGuildId'])) {
|
|
||||||
config['mainGuildId'] = [config['mainGuildId']];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure inboxServerPermission is always an array
|
|
||||||
if (! Array.isArray(config['inboxServerPermission'])) {
|
|
||||||
if (config['inboxServerPermission'] == null) {
|
|
||||||
config['inboxServerPermission'] = [];
|
|
||||||
} else {
|
|
||||||
config['inboxServerPermission'] = [config['inboxServerPermission']];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move greetingMessage/greetingAttachment to the guildGreetings object internally
|
// 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
|
// 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
|
// already have something set up in guildGreetings. This retains backwards compatibility while allowing you to override
|
||||||
|
@ -163,15 +139,71 @@ if (config.newThreadCategoryId) {
|
||||||
delete config.newThreadCategoryId;
|
delete config.newThreadCategoryId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn empty string options to null (i.e. "option=" without a value in config.ini)
|
// Delete empty string options (i.e. "option=" without a value in config.ini)
|
||||||
for (const [key, value] of Object.entries(config)) {
|
for (const [key, value] of Object.entries(config)) {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
config[key] = null;
|
delete config[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate config and assign defaults (if missing)
|
// Validate config and assign defaults (if missing)
|
||||||
const ajv = new Ajv({ useDefaults: true, coerceTypes: "array" });
|
const ajv = new Ajv({ useDefaults: true, coerceTypes: "array" });
|
||||||
|
|
||||||
|
// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820
|
||||||
|
const truthyValues = ["1", "true", "on"];
|
||||||
|
const falsyValues = ["0", "false", "off"];
|
||||||
|
ajv.addKeyword('coerceBoolean', {
|
||||||
|
compile(value) {
|
||||||
|
return (data, dataPath, parentData, parentKey) => {
|
||||||
|
if (! value) {
|
||||||
|
// Disabled -> no coercion
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270777250
|
||||||
|
// The "data" argument doesn't update within the same set of schemas inside "allOf",
|
||||||
|
// so we're referring to the original property instead.
|
||||||
|
// This also means we can't use { "type": "boolean" }, as it would test the un-updated data value.
|
||||||
|
const realData = parentData[parentKey];
|
||||||
|
|
||||||
|
if (typeof realData === "boolean") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (truthyValues.includes(realData)) {
|
||||||
|
parentData[parentKey] = true;
|
||||||
|
} else if (falsyValues.includes(realData)) {
|
||||||
|
parentData[parentKey] = false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ajv.addKeyword('multilineString', {
|
||||||
|
compile(value) {
|
||||||
|
return (data, dataPath, parentData, parentKey) => {
|
||||||
|
if (! value) {
|
||||||
|
// Disabled -> no coercion
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const realData = parentData[parentKey];
|
||||||
|
|
||||||
|
if (typeof realData === "string") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentData[parentKey] = realData.join("\n");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const validate = ajv.compile(schema);
|
const validate = ajv.compile(schema);
|
||||||
const configIsValid = validate(config);
|
const configIsValid = validate(config);
|
||||||
|
|
||||||
|
@ -186,5 +218,6 @@ if (! configIsValid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Configuration ok!");
|
console.log("Configuration ok!");
|
||||||
|
process.exit(0);
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
/**
|
/**
|
||||||
* @typedef {object} ModmailConfig
|
* @typedef {object} ModmailConfig
|
||||||
* @property {string} [token]
|
* @property {string} [token]
|
||||||
* @property {*} [mainGuildId]
|
* @property {array} [mainGuildId]
|
||||||
* @property {string} [mailGuildId]
|
* @property {string} [mailGuildId]
|
||||||
* @property {string} [logChannelId]
|
* @property {string} [logChannelId]
|
||||||
* @property {string} [prefix="!"]
|
* @property {string} [prefix="!"]
|
||||||
* @property {string} [snippetPrefix="!!"]
|
* @property {string} [snippetPrefix="!!"]
|
||||||
* @property {string} [snippetPrefixAnon="!!!"]
|
* @property {string} [snippetPrefixAnon="!!!"]
|
||||||
* @property {string} [status="Message me for help!"]
|
* @property {string} [status="Message me for help!"]
|
||||||
* @property {*} [responseMessage="Thank you for your message! Our mod team will reply to you here as soon as possible."]
|
* @property {string} [responseMessage="Thank you for your message! Our mod team will reply to you here as soon as possible."]
|
||||||
* @property {*} [closeMessage]
|
* @property {string} [closeMessage]
|
||||||
* @property {boolean} [allowUserClose=false]
|
* @property {boolean} [allowUserClose=false]
|
||||||
* @property {string} [newThreadCategoryId]
|
* @property {string} [newThreadCategoryId]
|
||||||
* @property {string} [mentionRole="here"]
|
* @property {string} [mentionRole="here"]
|
||||||
* @property {boolean} [pingOnBotMention=true]
|
* @property {boolean} [pingOnBotMention=true]
|
||||||
* @property {*} [botMentionResponse]
|
* @property {string} [botMentionResponse]
|
||||||
* @property {*} [inboxServerPermission]
|
* @property {array} [inboxServerPermission]
|
||||||
* @property {boolean} [alwaysReply=false]
|
* @property {boolean} [alwaysReply=false]
|
||||||
* @property {boolean} [alwaysReplyAnon=false]
|
* @property {boolean} [alwaysReplyAnon=false]
|
||||||
* @property {boolean} [useNicknames=false]
|
* @property {boolean} [useNicknames=false]
|
||||||
|
@ -30,18 +30,18 @@
|
||||||
* @property {boolean} [allowStaffEdit=false]
|
* @property {boolean} [allowStaffEdit=false]
|
||||||
* @property {boolean} [allowStaffDelete=false]
|
* @property {boolean} [allowStaffDelete=false]
|
||||||
* @property {boolean} [enableGreeting=false]
|
* @property {boolean} [enableGreeting=false]
|
||||||
* @property {*} [greetingMessage]
|
* @property {string} [greetingMessage]
|
||||||
* @property {string} [greetingAttachment]
|
* @property {string} [greetingAttachment]
|
||||||
* @property {*} [guildGreetings={}]
|
* @property {*} [guildGreetings={}]
|
||||||
* @property {number} [requiredAccountAge] Required account age to message Modmail, in hours
|
* @property {number} [requiredAccountAge] Required account age to message Modmail, in hours
|
||||||
* @property {*} [accountAgeDeniedMessage="Your Discord account is not old enough to contact modmail."]
|
* @property {string} [accountAgeDeniedMessage="Your Discord account is not old enough to contact modmail."]
|
||||||
* @property {number} [requiredTimeOnServer] Required time on server to message Modmail, in minutes
|
* @property {number} [requiredTimeOnServer] Required time on server to message Modmail, in minutes
|
||||||
* @property {*} [timeOnServerDeniedMessage="You haven't been a member of the server for long enough to contact modmail."]
|
* @property {string} [timeOnServerDeniedMessage="You haven't been a member of the server for long enough to contact modmail."]
|
||||||
* @property {boolean} [relaySmallAttachmentsAsAttachments=false]
|
* @property {boolean} [relaySmallAttachmentsAsAttachments=false]
|
||||||
* @property {number} [smallAttachmentLimit=2097152] Max size of attachment to relay directly. Default is 2MB.
|
* @property {number} [smallAttachmentLimit=2097152] Max size of attachment to relay directly. Default is 2MB.
|
||||||
* @property {string} [attachmentStorage="local"]
|
* @property {string} [attachmentStorage="local"]
|
||||||
* @property {string} [attachmentStorageChannelId]
|
* @property {string} [attachmentStorageChannelId]
|
||||||
* @property {*} [categoryAutomation]
|
* @property {*} [categoryAutomation={}]
|
||||||
* @property {boolean} [updateNotifications=true]
|
* @property {boolean} [updateNotifications=true]
|
||||||
* @property {array} [plugins=[]]
|
* @property {array} [plugins=[]]
|
||||||
* @property {*} [commandAliases]
|
* @property {*} [commandAliases]
|
||||||
|
|
|
@ -10,7 +10,36 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"multilineString": {
|
"multilineString": {
|
||||||
"$ref": "#/definitions/stringArray"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$comment": "See definition of multilineString in cfg.js",
|
||||||
|
"multilineString": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customBoolean": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"type": ["boolean", "string"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$comment": "See definition of coerceBoolean in cfg.js",
|
||||||
|
"coerceBoolean": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -51,7 +80,7 @@
|
||||||
"$ref": "#/definitions/multilineString"
|
"$ref": "#/definitions/multilineString"
|
||||||
},
|
},
|
||||||
"allowUserClose": {
|
"allowUserClose": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -63,7 +92,7 @@
|
||||||
"default": "here"
|
"default": "here"
|
||||||
},
|
},
|
||||||
"pingOnBotMention": {
|
"pingOnBotMention": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"botMentionResponse": {
|
"botMentionResponse": {
|
||||||
|
@ -74,60 +103,60 @@
|
||||||
"$ref": "#/definitions/stringArray"
|
"$ref": "#/definitions/stringArray"
|
||||||
},
|
},
|
||||||
"alwaysReply": {
|
"alwaysReply": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"alwaysReplyAnon": {
|
"alwaysReplyAnon": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"useNicknames": {
|
"useNicknames": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"ignoreAccidentalThreads": {
|
"ignoreAccidentalThreads": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"threadTimestamps": {
|
"threadTimestamps": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"allowMove": {
|
"allowMove": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"syncPermissionsOnMove": {
|
"syncPermissionsOnMove": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"typingProxy": {
|
"typingProxy": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"typingProxyReverse": {
|
"typingProxyReverse": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"mentionUserInThreadHeader": {
|
"mentionUserInThreadHeader": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"rolesInThreadHeader": {
|
"rolesInThreadHeader": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"allowStaffEdit": {
|
"allowStaffEdit": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"allowStaffDelete": {
|
"allowStaffDelete": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
|
|
||||||
"enableGreeting": {
|
"enableGreeting": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"greetingMessage": {
|
"greetingMessage": {
|
||||||
|
@ -164,7 +193,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"relaySmallAttachmentsAsAttachments": {
|
"relaySmallAttachmentsAsAttachments": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"smallAttachmentLimit": {
|
"smallAttachmentLimit": {
|
||||||
|
@ -194,7 +223,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"updateNotifications": {
|
"updateNotifications": {
|
||||||
"type": "boolean",
|
"$ref": "#/definitions/customBoolean",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
|
|
@ -4,5 +4,20 @@ const toJsdoc = require('json-schema-to-jsdoc');
|
||||||
const schema = require('./cfg.schema.json');
|
const schema = require('./cfg.schema.json');
|
||||||
const target = path.join(__dirname, 'cfg.jsdoc.js');
|
const target = path.join(__dirname, 'cfg.jsdoc.js');
|
||||||
|
|
||||||
const result = toJsdoc(schema);
|
// Fix up some custom types for the JSDoc conversion
|
||||||
|
const schemaCopy = JSON.parse(JSON.stringify(schema));
|
||||||
|
for (const propertyDef of Object.values(schemaCopy.properties)) {
|
||||||
|
if (propertyDef.$ref === "#/definitions/stringArray") {
|
||||||
|
propertyDef.type = "array";
|
||||||
|
delete propertyDef.$ref;
|
||||||
|
} else if (propertyDef.$ref === "#/definitions/customBoolean") {
|
||||||
|
propertyDef.type = "boolean";
|
||||||
|
delete propertyDef.$ref;
|
||||||
|
} else if (propertyDef.$ref === "#/definitions/multilineString") {
|
||||||
|
propertyDef.type = "string";
|
||||||
|
delete propertyDef.$ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = toJsdoc(schemaCopy);
|
||||||
fs.writeFileSync(target, result, { encoding: 'utf8' });
|
fs.writeFileSync(target, result, { encoding: 'utf8' });
|
||||||
|
|
Loading…
Reference in New Issue