Files
context-mod/src/Schema/OperatorConfig.json
FoxxMD 8cf30b6b7d refactor: Introduce staggered startup for bots/polling to decrease load on host/reddit and improve image comparison performance
* Implement staggered startup for bots (reddit accounts, top-level)
* Implement staggered startup for managers (subreddits) and subreddit polling
* Introduce random -1/+1 second to polling interval for every stream to ensure none are synced so there is no instantaneous spike in cpu/traffic/memory on host/reddit
* Add user-configurable stagger interval for shared mod polling
* Implement second image comparison approach with pixelmatch for reduced memory usage when image dimensions are exactly the same
* Use sharp to resize images to 400 width max when using resemblejs to reduce memory usage
2021-10-07 17:13:27 -04:00

783 lines
38 KiB
JSON

{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"BotConnection": {
"description": "Configuration required to connect to a CM Server",
"properties": {
"host": {
"description": "The hostname and port the CM Server is listening on EX `localhost:8085`",
"type": "string"
},
"secret": {
"description": "The **shared secret** used to sign API calls from the Client to the Server.\n\nThis value should be the same as what is specified in the target CM's `api.secret` configuration",
"type": "string"
}
},
"required": [
"host",
"secret"
],
"type": "object"
},
"BotInstanceJsonConfig": {
"description": "The configuration for an **individual reddit account** ContextMod will run as a bot.\n\nMultiple bot configs may be specified (one per reddit account).\n\n**NOTE:** If `bots` is not specified in a `FILE` then a default `bot` is generated using `ENV/ARG` values IE `CLIENT_ID`, etc...but if `bots` IS specified the default is not generated.",
"properties": {
"caching": {
"$ref": "#/definitions/OperatorCacheConfig",
"description": "Settings to configure the default caching behavior for this bot\n\nEvery setting not specified will default to what is specified by the global operator caching config"
},
"credentials": {
"$ref": "#/definitions/RedditCredentials",
"description": "Credentials required for the bot to interact with Reddit's API\n\nThese credentials will provided to both the API and Web interface unless otherwise specified with the `web.credentials` property\n\nRefer to the [required credentials table](https://github.com/FoxxMD/context-mod/blob/master/docs/operatorConfiguration.md#minimum-required-configuration) to see what is necessary to run the bot.",
"examples": [
{
"accessToken": "p75_1c467b2",
"clientId": "f4b4df1_9oiu",
"clientSecret": "34v5q1c564_yt7",
"redirectUri": "http://localhost:8085/callback",
"refreshToken": "34_f1w1v4"
}
]
},
"name": {
"type": "string"
},
"nanny": {
"description": "Settings related to managing heavy API usage.",
"properties": {
"hardLimit": {
"default": 50,
"description": "When `api limit remaining` reaches this number the application will pause all event polling until the api limit is reset.",
"examples": [
50
],
"type": "number"
},
"softLimit": {
"default": 250,
"description": "When `api limit remaining` reaches this number the application will attempt to put heavy-usage subreddits in a **slow mode** where activity processed is slowed to one every 1.5 seconds until the api limit is reset.",
"examples": [
250
],
"type": "number"
}
},
"type": "object"
},
"notifications": {
"$ref": "#/definitions/NotificationConfig",
"description": "Settings to configure 3rd party notifications for when behavior occurs"
},
"polling": {
"allOf": [
{
"$ref": "#/definitions/PollingDefaults"
},
{
"properties": {
"sharedMod": {
"default": false,
"description": "If set to `true` all subreddits polling unmoderated/modqueue with default polling settings will share a request to \"r/mod\"\notherwise each subreddit will poll its own mod view\n\n* ENV => `SHARE_MOD`\n* ARG => `--shareMod`",
"type": "boolean"
},
"stagger": {
"description": "If sharing a mod stream stagger pushing relevant Activities to individual subreddits.\n\nUseful when running many subreddits and rules are potentially cpu/memory/traffic heavy -- allows spreading out load",
"type": "number"
}
},
"type": "object"
}
],
"description": "Settings related to default polling configurations for subreddits"
},
"queue": {
"description": "Settings related to default configurations for queue behavior for subreddits",
"properties": {
"maxWorkers": {
"default": 1,
"description": "Set the number of maximum concurrent workers any subreddit can use.\n\nSubreddits may define their own number of max workers in their config but the application will never allow any subreddit's max workers to be larger than the operator\n\nNOTE: Do not increase this unless you are certain you know what you are doing! The default is suitable for the majority of use cases.",
"examples": [
1
],
"type": "number"
}
},
"type": "object"
},
"snoowrap": {
"description": "Settings to control some [Snoowrap](https://github.com/not-an-aardvark/snoowrap) behavior",
"properties": {
"debug": {
"description": "Manually set the debug status for snoowrap\n\nWhen snoowrap has `debug: true` it will log the http status response of reddit api requests to at the `debug` level\n\n* Set to `true` to always output\n* Set to `false` to never output\n\nIf not present or `null` will be set based on `logLevel`\n\n* ENV => `SNOO_DEBUG`\n* ARG => `--snooDebug`",
"type": "boolean"
},
"proxy": {
"description": "Proxy all requests to Reddit's API through this endpoint\n\n* ENV => `PROXY`\n* ARG => `--proxy <proxyEndpoint>`",
"examples": [
"http://localhost:4443"
],
"type": "string"
}
},
"type": "object"
},
"subreddits": {
"description": "Settings related to bot behavior for subreddits it is managing",
"properties": {
"dryRun": {
"default": false,
"description": "If `true` then all subreddits will run in dry run mode, overriding configurations\n\n* ENV => `DRYRUN`\n* ARG => `--dryRun`",
"examples": [
false
],
"type": "boolean"
},
"exclude": {
"description": "Names of subreddits the bot should NOT run, based on what subreddits it moderates\n\nThis setting is ignored if `names` is specified",
"examples": [
[
"mealtimevideos",
"programminghumor"
]
],
"items": {
"type": "string"
},
"type": "array"
},
"heartbeatInterval": {
"default": 300,
"description": "Interval, in seconds, to perform application heartbeat\n\nOn heartbeat the application does several things:\n\n* Log output with current api rate remaining and other statistics\n* Tries to retrieve and parse configurations for any subreddits with invalid configuration state\n* Restarts any bots stopped/paused due to polling issues, general errors, or invalid configs (if new config is valid)\n\n* ENV => `HEARTBEAT`\n* ARG => `--heartbeat <sec>`",
"examples": [
300
],
"type": "number"
},
"names": {
"description": "Names of subreddits for bot to run on\n\nIf not present or `null` bot will run on all subreddits it is a moderator of\n\n* ENV => `SUBREDDITS` (comma-separated)\n* ARG => `--subreddits <list...>`",
"examples": [
[
"mealtimevideos",
"programminghumor"
]
],
"items": {
"type": "string"
},
"type": "array"
},
"wikiConfig": {
"default": "botconfig/contextbot",
"description": "The default relative url to the ContextMod wiki page EX `https://reddit.com/r/subreddit/wiki/<path>`\n\n* ENV => `WIKI_CONFIG`\n* ARG => `--wikiConfig <path>`",
"examples": [
"botconfig/contextbot"
],
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
},
"CacheOptions": {
"additionalProperties": {
},
"description": "Configure granular settings for a cache provider with this object",
"properties": {
"auth_pass": {
"description": "(`redis`) the authentication passphrase (if enabled)",
"type": "string"
},
"db": {
"default": 0,
"description": "(`redis`) the db number to use",
"examples": [
0
],
"type": "number"
},
"host": {
"default": "localhost",
"description": "(`redis`) hostname",
"examples": [
"localhost"
],
"type": "string"
},
"max": {
"default": 500,
"description": "(`memory`) The maximum number of keys (unique cache calls) to store in cache\n\nWhen the maximum number of keys is reached the cache will being dropping the [least-recently-used](https://github.com/isaacs/node-lru-cache) key to keep the cache at `max` size.\n\nThis will determine roughly how large in **RAM** each `memory` cache can be, based on how large your `window` criteria are. Consider this example:\n\n* all `window` criteria in a subreddit's rules are `\"window\": 100`\n* `\"max\": 500`\n* Maximum size of **each** memory cache will be `500 x 100 activities = 50,000 activities`\n * So the shared cache would be max 50k activities and\n * Every additional private cache (when a subreddit configures their cache separately) will also be max 50k activities",
"examples": [
500
],
"type": "number"
},
"port": {
"default": 6379,
"description": "(`redis`) port to connect on",
"examples": [
6379
],
"type": "number"
},
"store": {
"$ref": "#/definitions/CacheProvider"
},
"ttl": {
"default": 60,
"description": "The default TTL, in seconds, for the cache provider.\n\nCan mostly be ignored since TTLs are defined for each cache object",
"examples": [
60
],
"type": "number"
}
},
"required": [
"store"
],
"type": "object"
},
"CacheProvider": {
"description": "Available cache providers",
"enum": [
"memory",
"none",
"redis"
],
"type": "string"
},
"DiscordProviderConfig": {
"properties": {
"name": {
"type": "string"
},
"type": {
"enum": [
"discord"
],
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"name",
"type",
"url"
],
"type": "object"
},
"NotificationConfig": {
"properties": {
"events": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/NotificationEventConfig"
},
{
"items": {
"enum": [
"configUpdated",
"eventActioned",
"pollingError",
"runStateChanged"
],
"type": "string"
},
"type": "array"
}
]
},
"type": "array"
},
"providers": {
"description": "A list of notification providers (Discord, etc..) to configure. Each object in the list is one provider. Multiple of the same provider can be provided but must have different names",
"items": {
"$ref": "#/definitions/DiscordProviderConfig"
},
"type": "array"
}
},
"required": [
"events",
"providers"
],
"type": "object"
},
"NotificationEventConfig": {
"properties": {
"providers": {
"items": {
"type": "string"
},
"type": "array"
},
"types": {
"items": {
"enum": [
"configUpdated",
"eventActioned",
"pollingError",
"runStateChanged"
],
"type": "string"
},
"type": "array"
}
},
"required": [
"providers",
"types"
],
"type": "object"
},
"OperatorCacheConfig": {
"properties": {
"actionedEventsDefault": {
"default": 25,
"description": "The **default** number of Events that the cache will store triggered result summaries for\n\nThese summaries are viewable through the Web UI.\n\nThe value specified cannot be larger than `actionedEventsMax` for the global/bot config (if set)",
"type": "number"
},
"actionedEventsMax": {
"default": 25,
"description": "The **maximum** number of Events that the cache should store triggered result summaries for\n\nThese summaries are viewable through the Web UI.\n\nThe value specified by a subreddit cannot be larger than the value set by the Operator for the global/bot config (if set)",
"type": "number"
},
"authorTTL": {
"default": 60,
"description": "Amount of time, in seconds, author activity history (Comments/Submission) should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache\n\n* ENV => `AUTHOR_TTL`\n* ARG => `--authorTTL <sec>`",
"examples": [
60
],
"type": [
"number",
"boolean"
]
},
"commentTTL": {
"default": 60,
"description": "Amount of time, in seconds, a comment should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
60
],
"type": [
"number",
"boolean"
]
},
"filterCriteriaTTL": {
"default": 60,
"description": "Amount of time, in seconds, to cache filter criteria results (`authorIs` and `itemIs` results)\n\nThis is especially useful if when polling high-volume comments and your checks rely on author/item filters\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
60
],
"type": [
"number",
"boolean"
]
},
"provider": {
"anyOf": [
{
"$ref": "#/definitions/CacheOptions"
},
{
"enum": [
"memory",
"none",
"redis"
],
"type": "string"
}
],
"description": "The cache provider and, optionally, a custom configuration for that provider\n\nIf not present or `null` provider will be `memory`.\n\nTo specify another `provider` but use its default configuration set this property to a string of one of the available providers: `memory`, `redis`, or `none`"
},
"submissionTTL": {
"default": 60,
"description": "Amount of time, in seconds, a submission should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
60
],
"type": [
"number",
"boolean"
]
},
"subredditTTL": {
"default": 600,
"description": "Amount of time, in seconds, a subreddit (attributes) should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
600
],
"type": [
"number",
"boolean"
]
},
"userNotesTTL": {
"default": 300,
"description": "Amount of time, in seconds, [Toolbox User Notes](https://www.reddit.com/r/toolbox/wiki/docs/usernotes) should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
300
],
"type": [
"number",
"boolean"
]
},
"wikiTTL": {
"default": 300,
"description": "Amount of time, in seconds, wiki content pages should be cached\n\n* If `0` or `true` will cache indefinitely (not recommended)\n* If `false` will not cache",
"examples": [
300
],
"type": [
"number",
"boolean"
]
}
},
"type": "object"
},
"PollingDefaults": {
"properties": {
"delayUntil": {
"description": "Delay processing Activity until it is `N` seconds old\n\nUseful if there are other bots that may process an Activity and you want this bot to run first/last/etc.\n\nIf the Activity is already `N` seconds old when it is initially retrieved no refresh of the Activity occurs (no API request is made) and it is immediately processed.",
"type": "number"
},
"interval": {
"default": 30,
"description": "Amount of time, in seconds, to wait between requests",
"examples": [
30
],
"type": "number"
},
"limit": {
"default": 50,
"description": "The maximum number of Activities to get on every request",
"examples": [
50
],
"type": "number"
}
},
"type": "object"
},
"RedditCredentials": {
"description": "Credentials required for the bot to interact with Reddit's API\n\nThese credentials will provided to both the API and Web interface unless otherwise specified with the `web.credentials` property\n\nRefer to the [required credentials table](https://github.com/FoxxMD/context-mod/blob/master/docs/operatorConfiguration.md#minimum-required-configuration) to see what is necessary to run the bot.",
"examples": [
{
"accessToken": "p75_1c467b2",
"clientId": "f4b4df1_9oiu",
"clientSecret": "34v5q1c564_yt7",
"redirectUri": "http://localhost:8085/callback",
"refreshToken": "34_f1w1v4"
}
],
"properties": {
"accessToken": {
"description": "Access token retrieved from authenticating an account with your Reddit Application\n\n* ENV => `ACCESS_TOKEN`\n* ARG => `--accessToken <token>`",
"examples": [
"p75_1c467b2"
],
"type": "string"
},
"clientId": {
"description": "Client ID for your Reddit application\n\n* ENV => `CLIENT_ID`\n* ARG => `--clientId <id>`",
"examples": [
"f4b4df1c7b2"
],
"type": "string"
},
"clientSecret": {
"description": "Client Secret for your Reddit application\n\n* ENV => `CLIENT_SECRET`\n* ARG => `--clientSecret <id>`",
"examples": [
"34v5q1c56ub"
],
"type": "string"
},
"refreshToken": {
"description": "Refresh token retrieved from authenticating an account with your Reddit Application\n\n* ENV => `REFRESH_TOKEN`\n* ARG => `--refreshToken <token>`",
"examples": [
"34_f1w1v4"
],
"type": "string"
}
},
"type": "object"
},
"WebCredentials": {
"description": "Separate credentials for the web interface can be provided when also running the api.\n\nAll properties not specified will default to values given in ENV/ARG credential properties\n\nRefer to the [required credentials table](https://github.com/FoxxMD/context-mod/blob/master/docs/operatorConfiguration.md#minimum-required-configuration) to see what is necessary for the web interface.",
"examples": [
{
"clientId": "f4b4df1_9oiu",
"clientSecret": "34v5q1c564_yt7",
"redirectUri": "http://localhost:8085/callback"
}
],
"properties": {
"clientId": {
"description": "Client ID for your Reddit application",
"examples": [
"f4b4df1_9oiu"
],
"type": "string"
},
"clientSecret": {
"description": "Client Secret for your Reddit application",
"examples": [
"34v5q1c564_yt7"
],
"type": "string"
},
"redirectUri": {
"description": "Redirect URI for your Reddit application\n\nUsed for:\n\n* accessing the web interface for monitoring bots\n* authenticating an account to use for a bot instance\n\n* ENV => `REDIRECT_URI`\n* ARG => `--redirectUri <uri>`",
"examples": [
"http://localhost:8085/callback"
],
"type": "string"
}
},
"type": "object"
}
},
"description": "Configuration for application-level settings IE for running the bot instance\n\n* To load a JSON configuration **from the command line** use the `-c` cli argument EX: `node src/index.js -c /path/to/JSON/config.json`\n* To load a JSON configuration **using an environmental variable** use `OPERATOR_CONFIG` EX: `OPERATOR_CONFIG=/path/to/JSON/config.json`",
"properties": {
"api": {
"description": "Configuration for the **Server** application. See [Architecture Documentation](https://github.com/FoxxMD/context-mod/blob/master/docs/serverClientArchitecture.md) for more info",
"properties": {
"friendly": {
"description": "A friendly name for this server. This will override `friendly` in `BotConnection` if specified.",
"type": "string"
},
"port": {
"default": 8095,
"description": "The port the server listens on for API requests",
"examples": [
8095
],
"type": "number"
},
"secret": {
"description": "The **shared secret** used to verify API requests come from an authenticated client.\n\nUse this same value for the `secret` value in a `BotConnection` object to connect to this Server",
"type": "string"
}
},
"type": "object"
},
"bots": {
"items": {
"$ref": "#/definitions/BotInstanceJsonConfig"
},
"type": "array"
},
"caching": {
"$ref": "#/definitions/OperatorCacheConfig",
"description": "Settings to configure the default caching behavior globally\n\nThese settings will be used by each bot, and subreddit, that does not specify their own"
},
"logging": {
"description": "Settings to configure global logging defaults",
"properties": {
"level": {
"default": "verbose",
"description": "The minimum log level to output. The log level set will output logs at its level **and all levels above it:**\n\n * `error`\n * `warn`\n * `info`\n * `verbose`\n * `debug`\n\n Note: `verbose` will display *a lot* of information on the status/result of run rules/checks/actions etc. which is very useful for testing configurations. Once your bot is stable changing the level to `info` will reduce log noise.\n\n * ENV => `LOG_LEVEL`\n * ARG => `--logLevel <level>`",
"enum": [
"debug",
"error",
"info",
"verbose",
"warn"
],
"examples": [
"verbose"
],
"type": "string"
},
"path": {
"description": "The absolute path to a directory where rotating log files should be stored.\n\n* If not present or `null` no log files will be created\n* If `true` logs will be stored at `[working directory]/logs`\n\n* ENV => `LOG_DIR`\n* ARG => `--logDir [dir]`",
"examples": [
"/var/log/contextmod"
],
"type": "string"
}
},
"type": "object"
},
"mode": {
"default": "all",
"description": "Mode to run ContextMod in\n\n* `all` (default) - Run the api and the web interface\n* `client` - Run web interface only\n* `server` - Run the api/bots only",
"enum": [
"all",
"client",
"server"
],
"type": "string"
},
"notifications": {
"$ref": "#/definitions/NotificationConfig",
"description": "Settings to configure 3rd party notifications for when ContextMod behavior occurs"
},
"operator": {
"description": "Settings related to the user(s) running this ContextMod instance and information on the bot",
"properties": {
"display": {
"description": "A **public** name to display to users of the web interface. Use this to help moderators using your bot identify who is the operator in case they need to contact you.\n\nLeave undefined for no public name to be displayed.\n\n* ENV => `OPERATOR_DISPLAY`\n* ARG => `--operatorDisplay <name>`",
"examples": [
"Moderators of r/MySubreddit"
],
"type": "string"
},
"name": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"description": "The name, or names, of the Reddit accounts, without prefix, that the operators of this bot uses.\n\nThis is used for showing more information in the web interface IE show all logs/subreddits if even not a moderator.\n\nEX -- User is /u/FoxxMD then `\"name\": [\"FoxxMD\"]`\n\n* ENV => `OPERATOR` (if list, comma-delimited)\n* ARG => `--operator <name...>`",
"examples": [
[
"FoxxMD",
"AnotherUser"
]
]
}
},
"type": "object"
},
"web": {
"description": "Settings for the web interface",
"properties": {
"caching": {
"anyOf": [
{
"$ref": "#/definitions/CacheOptions"
},
{
"enum": [
"memory",
"redis"
],
"type": "string"
}
],
"description": "Caching provider to use for session and invite data\n\nIf none is provided the top-level caching provider is used"
},
"clients": {
"description": "A list of CM Servers this Client should connect to.\n\nIf not specified a default `BotConnection` for this instance is generated",
"examples": [
[
{
"host": "localhost:8095",
"secret": "aRandomString"
}
]
],
"items": {
"$ref": "#/definitions/BotConnection"
},
"type": "array"
},
"credentials": {
"$ref": "#/definitions/WebCredentials",
"description": "Separate credentials for the web interface can be provided when also running the api.\n\nAll properties not specified will default to values given in ENV/ARG credential properties\n\nRefer to the [required credentials table](https://github.com/FoxxMD/context-mod/blob/master/docs/operatorConfiguration.md#minimum-required-configuration) to see what is necessary for the web interface.",
"examples": [
{
"clientId": "f4b4df1_9oiu",
"clientSecret": "34v5q1c564_yt7",
"redirectUri": "http://localhost:8085/callback"
}
]
},
"invites": {
"description": "Settings related to oauth flow invites",
"properties": {
"maxAge": {
"default": 0,
"description": "Number of seconds an invite should be valid for\n\n If `0` or not specified (default) invites do not expire",
"examples": [
0
],
"type": "number"
}
},
"type": "object"
},
"logLevel": {
"description": "The default log level to filter to in the web interface\n\nIf not specified or `null` will be same as global `logLevel`",
"enum": [
"debug",
"error",
"info",
"verbose",
"warn"
],
"type": "string"
},
"maxLogs": {
"default": 200,
"description": "Maximum number of log statements to keep in memory for each subreddit",
"examples": [
200
],
"type": "number"
},
"operators": {
"description": "The name, or names, of the Reddit accounts, without prefix, that the operators of this **web interface** uses.\n\n**Note:** This is **not the same** as the top-level `operator` property. This allows specified users to see the status of all `clients` but **not** access to them -- that must still be specified in the `operator.name` property in the configuration of each bot.\n\n\nEX -- User is /u/FoxxMD then `\"name\": [\"FoxxMD\"]`",
"examples": [
[
"FoxxMD",
"AnotherUser"
]
],
"items": {
"type": "string"
},
"type": "array"
},
"port": {
"default": 8085,
"description": "The port for the web interface\n\n* ENV => `PORT`\n* ARG => `--port <number>`",
"examples": [
8085
],
"type": "number"
},
"session": {
"description": "Settings to configure the behavior of user sessions -- the session is what the web interface uses to identify logged in users.",
"properties": {
"maxAge": {
"default": 86400,
"description": "Number of seconds a session should be valid for.\n\nDefault is 1 day",
"examples": [
86400
],
"type": "number"
},
"secret": {
"description": "The secret value used to encrypt session data\n\nIf provider is persistent (`redis`) specifying a value here will ensure sessions are valid between application restarts\n\nWhen not present or `null` a random string is generated on application start",
"examples": [
"definitelyARandomString"
],
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
},
"type": "object"
}