feat(flow): Implement max goto depth configuration

This commit is contained in:
FoxxMD
2022-02-22 10:42:52 -05:00
parent bca9c96468
commit 5f6e63542b
11 changed files with 486 additions and 444 deletions

View File

@@ -15,11 +15,11 @@ import {
USER
} from "../Common/interfaces";
import {
createRetryHandler,
createRetryHandler, difference,
formatNumber, getExceptionMessage, getUserAgent,
mergeArr,
parseBool,
parseDuration, parseMatchMessage,
parseDuration, parseMatchMessage, parseRedditEntity,
parseSubredditName, RetryOptions,
sleep,
snooLogWrapper
@@ -79,6 +79,8 @@ class Bot {
cacheManager: BotResourcesManager;
config: BotInstanceConfig;
getBotName = () => {
return this.botName;
}
@@ -131,6 +133,7 @@ class Bot {
}
} = config;
this.config = config;
this.dryRun = parseBool(dryRun) === true ? true : undefined;
this.softLimit = softLimit;
this.hardLimit = hardLimit;
@@ -350,6 +353,31 @@ class Bot {
}
}
const {
subreddits: {
overrides = [],
} = {}
} = this.config;
if(overrides.length > 0) {
// check for overrides that don't match subs to run and warn operator
const subsToRunNames = subsToRun.map(x => x.display_name.toLowerCase());
const normalizedOverrideNames = overrides.reduce((acc: string[], curr) => {
try {
const ent = parseRedditEntity(curr.name);
return acc.concat(ent.name.toLowerCase());
} catch (e) {
this.logger.warn(new ErrorWithCause(`Could not use subreddit override because name was not valid: ${curr.name}`, {cause: e}));
return acc;
}
}, []);
const notMatched = difference(normalizedOverrideNames, subsToRunNames);
if(notMatched.length > 0) {
this.logger.warn(`There are overrides defined for subreddits the bot is not running. Check your spelling! Overrides not matched: ${notMatched.join(', ')}`);
}
}
// get configs for subs we want to run on and build/validate them
for (const sub of subsToRun) {
try {
@@ -487,6 +515,29 @@ class Bot {
}
createManager(sub: Subreddit): Manager {
const {
flowControlDefaults: {
maxGotoDepth: botMaxDefault
} = {},
subreddits: {
overrides = [],
} = {}
} = this.config;
const override = overrides.find(x => {
const configName = parseRedditEntity(x.name).name;
if(configName !== undefined) {
return configName.toLowerCase() === sub.display_name.toLowerCase();
}
return false;
});
const {
flowControlDefaults: {
maxGotoDepth: subMax = undefined,
} = {}
} = override || {};
const manager = new Manager(sub, this.client, this.logger, this.cacheManager, {
dryRun: this.dryRun,
sharedStreams: this.sharedStreams,
@@ -494,6 +545,7 @@ class Bot {
botName: this.botName as string,
maxWorkers: this.maxWorkers,
filterCriteriaDefaults: this.filterCriteriaDefaults,
maxGotoDepth: subMax ?? botMaxDefault
});
// all errors from managers will count towards bot-level retry count
manager.on('error', async (err) => await this.panicOnRetries(err));

View File

@@ -1467,6 +1467,13 @@ export interface FilterCriteriaDefaults {
authorIsBehavior?: FilterCriteriaDefaultBehavior
}
export interface SubredditOverrides {
name: string
flowControlDefaults?: {
maxGotoDepth?: number
}
}
/**
* The configuration for an **individual reddit account** ContextMod will run as a bot.
*
@@ -1504,6 +1511,10 @@ export interface BotInstanceJsonConfig {
postCheckBehaviorDefaults?: PostBehavior
flowControlDefaults?: {
maxGotoDepth?: number
}
/**
* Settings related to bot behavior for subreddits it is managing
* */
@@ -1564,6 +1575,8 @@ export interface BotInstanceJsonConfig {
* @examples [300]
* */
heartbeatInterval?: number,
overrides?: SubredditOverrides[]
}
/**
@@ -1600,22 +1613,23 @@ export interface BotInstanceJsonConfig {
* Useful when running many subreddits and rules are potentially cpu/memory/traffic heavy -- allows spreading out load
* */
stagger?: number,
},
}
/**
* Settings related to default configurations for queue behavior for subreddits
* */
queue?: {
/**
* Set the number of maximum concurrent workers any subreddit can use.
*
* Subreddits 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
*
* NOTE: Do not increase this unless you are certain you know what you are doing! The default is suitable for the majority of use cases.
*
* @default 1
* @examples [1]
* */
maxWorkers?: number,
/**
* Set the number of maximum concurrent workers any subreddit can use.
*
* Subreddits 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
*
* NOTE: Do not increase this unless you are certain you know what you are doing! The default is suitable for the majority of use cases.
*
* @default 1
* @examples [1]
* */
maxWorkers?: number,
}
/**
@@ -1885,6 +1899,7 @@ export interface BotInstanceConfig extends BotInstanceJsonConfig {
dryRun?: boolean,
wikiConfig: string,
heartbeatInterval: number,
overrides?: SubredditOverrides[]
},
polling: {
shared: PollOn[],

View File

@@ -880,8 +880,10 @@ export const buildBotConfig = (data: BotInstanceJsonConfig, opConfig: OperatorCo
hardLimit = 50
} = {},
snoowrap = snoowrapOp,
flowControlDefaults,
credentials = {},
subreddits: {
overrides = [],
names = [],
exclude = [],
wikiConfig = 'botconfig/contextbot',
@@ -990,6 +992,7 @@ export const buildBotConfig = (data: BotInstanceJsonConfig, opConfig: OperatorCo
return {
name: botName,
snoowrap: snoowrap || {},
flowControlDefaults,
filterCriteriaDefaults,
postCheckBehaviorDefaults,
subreddits: {
@@ -998,6 +1001,7 @@ export const buildBotConfig = (data: BotInstanceJsonConfig, opConfig: OperatorCo
wikiConfig,
heartbeatInterval,
dryRun,
overrides,
},
credentials: botCreds,
caching: botCache,

View File

@@ -108,6 +108,10 @@ export class Run {
triggered: false,
checkResults: [],
}
const {
maxGotoDepth = 1,
gotoContext: optGotoContext = '',
} = options || {};
if(!this.enabled) {
runResult.error = 'Not enabled';
@@ -126,7 +130,7 @@ export class Run {
return [{...runResult, reason: msg}, postBehavior];
}
let gotoContext = options?.gotoContext ?? '';
let gotoContext = optGotoContext;
const checks = isSubmission(activity) ? this.submissionChecks : this.commentChecks;
let continueCheckIteration = true;
let checkIndex = 0;
@@ -163,10 +167,11 @@ export class Run {
let check: Check;
if (gotoContext !== '') {
const [runName, checkName] = gotoContext.split('.');
if(hitGotos.includes(checkName)) {
throw new Error(`The check specified in goto "${gotoContext}" has already been hit once. This indicates a possible endless loop may occur so CM will terminate processing this activity to save you from yourself!`);
}
hitGotos.push(checkName);
if(hitGotos.filter(x => x === gotoContext).length > maxGotoDepth) {
throw new Error(`The check specified in goto "${gotoContext}" has been triggered ${hitGotos.filter(x => x === gotoContext).length} times which is more than the max allowed for any single goto (${maxGotoDepth}).
This indicates a possible endless loop may occur so CM will terminate processing this activity to save you from yourself! The max triggered depth can be configured by the operator.`);
}
const gotoIndex = checks.findIndex(x => normalizeName(x.name) === normalizeName(checkName));
if (gotoIndex !== -1) {
if (gotoIndex > checkIndex) {

View File

@@ -438,21 +438,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",

View File

@@ -192,21 +192,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -428,21 +425,18 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -684,21 +678,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -784,21 +775,18 @@
"description": "Customize the footer for Actions that send replies (Comment/Ban)\n\nIf `false` no footer is appended\n\nIf `string` the value is rendered as markdown or will use `wiki:` parser the same way `content` properties on Actions are rendered with [templating](https://github.com/FoxxMD/context-mod#action-templating).\n\nIf footer is `undefined` (not set) the default footer will be used:\n\n> *****\n> This action was performed by [a bot.] Mention a moderator or [send a modmail] if you any ideas, questions, or concerns about this action.\n\n*****\n\nThe following properties are available for [templating](https://github.com/FoxxMD/context-mod#action-templating):\n```\nsubName => name of subreddit Action was performed in (EX 'mealtimevideos')\npermaLink => The permalink for the Activity the Action was performed on EX https://reddit.com/r/yourSub/comments/o1h0i0/title_name/1v3b7x\nmodmaiLink => An encoded URL that will open a new message to your subreddit with the Action permalink appended to the body\nbotLink => A permalink to the FAQ for this bot.\n```\nIf you use your own footer or no footer **please link back to the bot FAQ** using the `{{botLink}}` property in your content :)"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -1088,21 +1076,18 @@
"description": "Customize the footer for Actions that send replies (Comment/Ban)\n\nIf `false` no footer is appended\n\nIf `string` the value is rendered as markdown or will use `wiki:` parser the same way `content` properties on Actions are rendered with [templating](https://github.com/FoxxMD/context-mod#action-templating).\n\nIf footer is `undefined` (not set) the default footer will be used:\n\n> *****\n> This action was performed by [a bot.] Mention a moderator or [send a modmail] if you any ideas, questions, or concerns about this action.\n\n*****\n\nThe following properties are available for [templating](https://github.com/FoxxMD/context-mod#action-templating):\n```\nsubName => name of subreddit Action was performed in (EX 'mealtimevideos')\npermaLink => The permalink for the Activity the Action was performed on EX https://reddit.com/r/yourSub/comments/o1h0i0/title_name/1v3b7x\nmodmaiLink => An encoded URL that will open a new message to your subreddit with the Action permalink appended to the body\nbotLink => A permalink to the FAQ for this bot.\n```\nIf you use your own footer or no footer **please link back to the bot FAQ** using the `{{botLink}}` property in your content :)"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -1501,20 +1486,17 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
]
}
]
},
"type": "array"
},
"itemIsBehavior": {
"description": "Determine how itemIs defaults behave when itemIs is present on the check\n\n* merge => adds defaults to check's itemIs\n* replace => check itemIs will replace defaults (no defaults used)",
@@ -1575,21 +1557,18 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -1755,21 +1734,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1904,21 +1880,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -2003,21 +1976,18 @@
"description": "Customize the footer for Actions that send replies (Comment/Ban)\n\nIf `false` no footer is appended\n\nIf `string` the value is rendered as markdown or will use `wiki:` parser the same way `content` properties on Actions are rendered with [templating](https://github.com/FoxxMD/context-mod#action-templating).\n\nIf footer is `undefined` (not set) the default footer will be used:\n\n> *****\n> This action was performed by [a bot.] Mention a moderator or [send a modmail] if you any ideas, questions, or concerns about this action.\n\n*****\n\nThe following properties are available for [templating](https://github.com/FoxxMD/context-mod#action-templating):\n```\nsubName => name of subreddit Action was performed in (EX 'mealtimevideos')\npermaLink => The permalink for the Activity the Action was performed on EX https://reddit.com/r/yourSub/comments/o1h0i0/title_name/1v3b7x\nmodmaiLink => An encoded URL that will open a new message to your subreddit with the Action permalink appended to the body\nbotLink => A permalink to the FAQ for this bot.\n```\nIf you use your own footer or no footer **please link back to the bot FAQ** using the `{{botLink}}` property in your content :)"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -2279,21 +2249,18 @@
"description": "When comparing submissions detect if the reference submission is an image and do a pixel-comparison to other detected image submissions.\n\n**Note:** This is an **experimental feature**"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -2554,21 +2521,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -2635,21 +2599,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -2755,21 +2716,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"keepRemoved": {
"default": false,
@@ -2902,21 +2860,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -3117,21 +3072,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -3266,21 +3218,18 @@
"description": "Set the default filter criteria for all checks. If this property is specified it will override any defaults passed from the bot's config\n\nDefault behavior is to exclude all mods and automoderator from checks"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the check.\n\nIf any set of criteria passes the Check will be run. If the criteria fails then the Check will fail.\n\n* @examples [[{\"over_18\": true, \"removed': false}]]",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the check.\n\nIf any set of criteria passes the Check will be run. If the criteria fails then the Check will fail.\n\n* @examples [[{\"over_18\": true, \"removed': false}]]"
}
]
},
"type": "array"
},
"name": {
"description": "Friendly name for this Run EX \"flairsRun\"\n\nCan only contain letters, numbers, underscore, spaces, and dashes",
@@ -3830,21 +3779,18 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",
@@ -3928,21 +3874,18 @@
"type": "boolean"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Action.\n\nIf any set of criteria passes the Action will be run."
}
]
},
"type": "array"
},
"kind": {
"description": "The type of action that will be performed",

View File

@@ -245,6 +245,14 @@
"$ref": "#/definitions/FilterCriteriaDefaults",
"description": "Define the default behavior for all filter criteria on all checks in all subreddits\n\nDefaults to exclude mods and automoderator from checks"
},
"flowControlDefaults": {
"properties": {
"maxGotoDepth": {
"type": "number"
}
},
"type": "object"
},
"name": {
"type": "string"
},
@@ -385,6 +393,12 @@
},
"type": "array"
},
"overrides": {
"items": {
"$ref": "#/definitions/SubredditOverrides"
},
"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>`",
@@ -583,20 +597,17 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
]
}
]
},
"type": "array"
},
"itemIsBehavior": {
"description": "Determine how itemIs defaults behave when itemIs is present on the check\n\n* merge => adds defaults to check's itemIs\n* replace => check itemIs will replace defaults (no defaults used)",
@@ -1221,6 +1232,25 @@
},
"type": "object"
},
"SubredditOverrides": {
"properties": {
"flowControlDefaults": {
"properties": {
"maxGotoDepth": {
"type": "number"
}
},
"type": "object"
},
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"ThirdPartyCredentialsJsonConfig": {
"additionalProperties": {},
"properties": {

View File

@@ -366,21 +366,18 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -622,21 +619,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -921,21 +915,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1127,21 +1118,18 @@
"description": "When comparing submissions detect if the reference submission is an image and do a pixel-comparison to other detected image submissions.\n\n**Note:** This is an **experimental feature**"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1402,21 +1390,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1523,21 +1508,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"keepRemoved": {
"default": false,
@@ -1799,21 +1781,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",

View File

@@ -340,21 +340,18 @@
"type": "string"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -596,21 +593,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -895,21 +889,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1101,21 +1092,18 @@
"description": "When comparing submissions detect if the reference submission is an image and do a pixel-comparison to other detected image submissions.\n\n**Note:** This is an **experimental feature**"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1376,21 +1364,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",
@@ -1497,21 +1482,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"keepRemoved": {
"default": false,
@@ -1773,21 +1755,18 @@
"type": "array"
},
"itemIs": {
"anyOf": [
{
"items": {
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped.",
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
"type": "array"
},
{
"items": {
{
"$ref": "#/definitions/CommentState"
},
"type": "array"
}
],
"description": "A list of criteria to test the state of the `Activity` against before running the Rule.\n\nIf any set of criteria passes the Rule will be run. If the criteria fails then the Rule is skipped."
}
]
},
"type": "array"
},
"kind": {
"description": "The kind of rule to run",

View File

@@ -66,6 +66,7 @@ export interface runCheckOptions {
refresh?: boolean,
force?: boolean,
gotoContext?: string
maxGotoDepth?: number
}
export interface CheckTask {
@@ -76,8 +77,9 @@ export interface CheckTask {
export interface RuntimeManagerOptions extends ManagerOptions {
sharedStreams?: PollOn[];
wikiLocation?: string;
botName: string;
maxWorkers: number;
botName?: string;
maxWorkers?: number;
maxGotoDepth?: number
}
interface QueuedIdentifier {
@@ -128,6 +130,7 @@ export class Manager extends EventEmitter {
queuedItemsMeta: QueuedIdentifier[] = [];
globalMaxWorkers: number;
subMaxWorkers?: number;
maxGotoDepth: number;
displayLabel: string;
currentLabels: string[] = [];
@@ -206,10 +209,19 @@ export class Manager extends EventEmitter {
return this.displayLabel;
}
constructor(sub: Subreddit, client: ExtendedSnoowrap, logger: Logger, cacheManager: BotResourcesManager, opts: RuntimeManagerOptions = {botName: 'ContextMod', maxWorkers: 1}) {
constructor(sub: Subreddit, client: ExtendedSnoowrap, logger: Logger, cacheManager: BotResourcesManager, opts: RuntimeManagerOptions) {
super();
const {dryRun, sharedStreams = [], wikiLocation = 'botconfig/contextbot', botName, maxWorkers, filterCriteriaDefaults, postCheckBehaviorDefaults} = opts;
const {
dryRun,
sharedStreams = [],
wikiLocation = 'botconfig/contextbot',
botName = 'ContextMod',
maxWorkers = 1,
maxGotoDepth = 1,
filterCriteriaDefaults,
postCheckBehaviorDefaults
} = opts || {};
this.displayLabel = opts.nickname || `${sub.display_name_prefixed}`;
const getLabels = this.getCurrentLabels;
const getDisplay = this.getDisplay;
@@ -237,6 +249,7 @@ export class Manager extends EventEmitter {
this.subreddit = sub;
this.client = client;
this.botName = botName;
this.maxGotoDepth = maxGotoDepth;
this.globalMaxWorkers = maxWorkers;
this.notificationManager = new NotificationManager(this.logger, this.subreddit, this.displayLabel, botName);
this.cacheManager = cacheManager;
@@ -245,6 +258,8 @@ export class Manager extends EventEmitter {
this.queue.pause();
this.firehose = this.generateFirehose();
this.logger.info(`Max GOTO Depth: ${this.maxGotoDepth}`);
this.eventsSampleInterval = setInterval((function(self) {
return function() {
const et = self.resources !== undefined ? self.resources.stats.historical.allTime.eventsCheckedTotal : 0;
@@ -734,10 +749,11 @@ export class Manager extends EventEmitter {
while(continueRunIteration && (runIndex < this.runs.length || gotoContext !== '')) {
let currRun: Run;
if(gotoContext !== '') {
if(hitGotos.includes(gotoContext)) {
throw new Error(`The goto "${gotoContext}" has already been hit once. This indicates a possible endless loop may occur so CM will terminate processing this activity to save you from yourself!`);
}
hitGotos.push(gotoContext);
if(hitGotos.filter(x => x === gotoContext).length > this.maxGotoDepth) {
throw new Error(`The goto "${gotoContext}" has been triggered ${hitGotos.filter(x => x === gotoContext).length} times which is more than the max allowed for any single goto (${this.maxGotoDepth}).
This indicates a possible endless loop may occur so CM will terminate processing this activity to save you from yourself! The max triggered depth can be configured by the operator.`);
}
const [runName] = gotoContext.split('.');
const gotoIndex = this.runs.findIndex(x => normalizeName(x.name) === normalizeName(runName));
if(gotoIndex !== -1) {
@@ -761,7 +777,7 @@ export class Manager extends EventEmitter {
currRun = this.runs[runIndex];
}
const [runResult, postBehavior] = await currRun.handle(item,allRuleResults, runResults.filter(x => x.name === currRun.name), {...options, gotoContext});
const [runResult, postBehavior] = await currRun.handle(item,allRuleResults, runResults.filter(x => x.name === currRun.name), {...options, gotoContext, maxGotoDepth: this.maxGotoDepth});
runResults.push(runResult);
allRuleResults = allRuleResults.concat(determineNewResults(allRuleResults, (runResult.checkResults ?? []).map(x => x.ruleResults).flat()));

View File

@@ -1610,6 +1610,28 @@ export const intersect = (a: Array<any>, b: Array<any>) => {
const intersection = new Set([...setA].filter(x => setB.has(x)));
return Array.from(intersection);
}
/**
* @see https://stackoverflow.com/a/64245521/1469797
* */
function *setMinus(A: Array<any>, B: Array<any>) {
const setA = new Set(A);
const setB = new Set(B);
for (const v of setB.values()) {
if (!setA.delete(v)) {
yield v;
}
}
for (const v of setA.values()) {
yield v;
}
}
export const difference = (a: Array<any>, b: Array<any>) => {
return Array.from(setMinus(a, b));
}
export const snooLogWrapper = (logger: Logger) => {
return {