mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-04-19 03:00:07 -04:00
Add dryrun setting to help with testing
Can be configured at action, check, subreddit, or app level
This commit is contained in:
@@ -235,6 +235,7 @@ Options:
|
||||
-n, --snooDebug Set Snoowrap to debug (default: process.env.SNOO_DEBUG || false)
|
||||
--authorTTL <ms> Set the TTL (ms) for the Author Activities shared cache (default: process.env.AUTHOR_TTL || 10000)
|
||||
--disableCache Disable caching for all subreddits
|
||||
--dryRun Override dryRun=true for all checks/actions on all subreddits (default: process.env.DRYRUN)
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
|
||||
@@ -8,21 +8,17 @@ import {Logger} from "winston";
|
||||
|
||||
export function actionFactory
|
||||
(config: ActionJson, logger: Logger, subredditName: string): Action {
|
||||
let cfg;
|
||||
switch (config.kind) {
|
||||
case 'comment':
|
||||
cfg = config as CommentActionJson;
|
||||
return new CommentAction({...cfg, logger, subredditName});
|
||||
return new CommentAction({...config as CommentActionJson, logger, subredditName});
|
||||
case 'lock':
|
||||
return new LockAction({logger, subredditName});
|
||||
return new LockAction({...config, logger, subredditName});
|
||||
case 'remove':
|
||||
return new RemoveAction({logger, subredditName});
|
||||
return new RemoveAction({...config, logger, subredditName});
|
||||
case 'report':
|
||||
cfg = config as ReportActionJson;
|
||||
return new ReportAction({...cfg, logger, subredditName});
|
||||
return new ReportAction({...config as ReportActionJson, logger, subredditName});
|
||||
case 'flair':
|
||||
cfg = config as FlairActionJson;
|
||||
return new FlairAction({...cfg, logger, subredditName});
|
||||
return new FlairAction({...config as FlairActionJson, logger, subredditName});
|
||||
default:
|
||||
throw new Error('rule "kind" was not recognized.');
|
||||
}
|
||||
|
||||
@@ -37,13 +37,15 @@ export class CommentAction extends Action {
|
||||
const reply: Comment = await item.reply(renderedContent);
|
||||
if (this.lock) {
|
||||
if(item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
if(!this.dryRun) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Snoowrap does not support locking Comments');
|
||||
}
|
||||
}
|
||||
if (this.distinguish) {
|
||||
if (this.distinguish && !this.dryRun) {
|
||||
// @ts-ignore
|
||||
await reply.distinguish({sticky: this.sticky});
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ export class LockAction extends Action {
|
||||
|
||||
async process(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
if (item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
if(!this.dryRun) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Snoowrap does not support locking Comments');
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ export class RemoveAction extends Action {
|
||||
}
|
||||
|
||||
async process(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
// @ts-ignore
|
||||
await item.remove();
|
||||
if(!this.dryRun) {
|
||||
// @ts-ignore
|
||||
await item.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ export class ReportAction extends Action {
|
||||
const renderedContent = await renderContent(content, item, ruleResults);
|
||||
this.logger.verbose(`Contents:\r\n${renderedContent}`);
|
||||
const truncatedContent = reportTrunc(renderedContent);
|
||||
// @ts-ignore
|
||||
await item.report({reason: truncatedContent});
|
||||
if(!this.dryRun) {
|
||||
// @ts-ignore
|
||||
await item.report({reason: truncatedContent});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@ export class FlairAction extends Action {
|
||||
|
||||
async process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
if (item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.assignFlair({text: this.text, cssClass: this.css})
|
||||
if(!this.dryRun) {
|
||||
// @ts-ignore
|
||||
await item.assignFlair({text: this.text, cssClass: this.css})
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Cannot flair Comment');
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@ export abstract class Action {
|
||||
name?: string;
|
||||
logger: Logger;
|
||||
cache: SubredditCache;
|
||||
dryRun: boolean;
|
||||
|
||||
constructor(options: ActionOptions) {
|
||||
const {
|
||||
name = this.getKind(),
|
||||
logger,
|
||||
subredditName
|
||||
subredditName,
|
||||
dryRun = false,
|
||||
} = options;
|
||||
|
||||
this.name = name;
|
||||
this.dryRun = dryRun;
|
||||
this.cache = CacheManager.get(subredditName);
|
||||
const uniqueName = this.name === this.getKind() ? this.getKind() : `${this.getKind()} - ${this.name}`;
|
||||
this.logger = logger.child({labels: ['Action', uniqueName]});
|
||||
@@ -25,15 +28,14 @@ export abstract class Action {
|
||||
|
||||
async handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
await this.process(item, ruleResults);
|
||||
this.logger.debug('Done');
|
||||
this.logger.debug(`${this.dryRun ? 'DRYRUN - ' : ''}Done`);
|
||||
}
|
||||
|
||||
abstract process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ActionOptions {
|
||||
name?: string;
|
||||
logger: Logger,
|
||||
export interface ActionOptions extends ActionConfig {
|
||||
logger: Logger;
|
||||
subredditName: string;
|
||||
}
|
||||
|
||||
@@ -46,6 +48,12 @@ export interface ActionConfig {
|
||||
* @pattern ^[a-zA-Z]([\w -]*[\w])?$
|
||||
* */
|
||||
name?: string;
|
||||
/**
|
||||
* If `true` the Action will not make the API request to Reddit to perform its action.
|
||||
*
|
||||
* @default false
|
||||
* */
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface ActionJson extends ActionConfig {
|
||||
|
||||
@@ -24,6 +24,7 @@ export class App {
|
||||
subManagers: Manager[] = [];
|
||||
logger: Logger;
|
||||
wikiLocation: string;
|
||||
dryRun?: true | undefined;
|
||||
|
||||
constructor(options: any = {}) {
|
||||
const {
|
||||
@@ -36,6 +37,7 @@ export class App {
|
||||
logLevel,
|
||||
wikiConfig,
|
||||
snooDebug = process.env.SNOO_DEBUG,
|
||||
dryRun = process.env.DRYRUN,
|
||||
version,
|
||||
authorTTL,
|
||||
disableCache = false,
|
||||
@@ -44,6 +46,7 @@ export class App {
|
||||
CacheManager.authorTTL = authorTTL;
|
||||
CacheManager.enabled = !disableCache;
|
||||
|
||||
this.dryRun = dryRun === true || dryRun === 'true' ? true : undefined;
|
||||
this.wikiLocation = wikiConfig;
|
||||
|
||||
const consoleTransport = new transports.Console();
|
||||
@@ -89,6 +92,10 @@ export class App {
|
||||
|
||||
this.logger = winston.loggers.get('default');
|
||||
|
||||
if(this.dryRun) {
|
||||
this.logger.info('Running in DRYRUN mode');
|
||||
}
|
||||
|
||||
let subredditsArg = [];
|
||||
if (subreddits !== undefined) {
|
||||
if (Array.isArray(subreddits)) {
|
||||
@@ -175,7 +182,7 @@ export class App {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
subSchedule.push(new Manager(sub, this.client, this.logger, json));
|
||||
subSchedule.push(new Manager(sub, this.client, this.logger, json, {dryRun: this.dryRun}));
|
||||
} catch (err) {
|
||||
this.logger.error(`[${sub.display_name_prefixed}] Config was not valid`, undefined, err);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export class Check implements ICheck {
|
||||
condition: JoinOperands;
|
||||
rules: Array<RuleSet | Rule> = [];
|
||||
logger: Logger;
|
||||
dryRun?: boolean;
|
||||
|
||||
constructor(options: CheckOptions) {
|
||||
const {
|
||||
@@ -28,7 +29,8 @@ export class Check implements ICheck {
|
||||
condition = 'AND',
|
||||
rules = [],
|
||||
actions = [],
|
||||
subredditName
|
||||
subredditName,
|
||||
dryRun,
|
||||
} = options;
|
||||
|
||||
this.logger = options.logger.child({labels: [`Check ${name}`]}, mergeArr);
|
||||
@@ -38,6 +40,7 @@ export class Check implements ICheck {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.condition = condition;
|
||||
this.dryRun = dryRun;
|
||||
for (const r of rules) {
|
||||
if (r instanceof Rule || r instanceof RuleSet) {
|
||||
this.rules.push(r);
|
||||
@@ -73,7 +76,7 @@ export class Check implements ICheck {
|
||||
let valid = ajv.validate(ActionSchema, a);
|
||||
if (valid) {
|
||||
const aj = a as ActionJson;
|
||||
this.actions.push(actionFactory(aj, this.logger, subredditName));
|
||||
this.actions.push(actionFactory({...aj, dryRun: this.dryRun || aj.dryRun}, this.logger, subredditName));
|
||||
// @ts-ignore
|
||||
a.logger = this.logger;
|
||||
} else {
|
||||
@@ -115,11 +118,11 @@ export class Check implements ICheck {
|
||||
}
|
||||
|
||||
async runActions(item: Submission | Comment, ruleResults: RuleResult[]): Promise<void> {
|
||||
this.logger.debug('Running Actions');
|
||||
this.logger.debug(`${this.dryRun ? 'DRYRUN - ' : ''}Running Actions`);
|
||||
for (const a of this.actions) {
|
||||
await a.handle(item, ruleResults);
|
||||
}
|
||||
this.logger.info('Ran Actions');
|
||||
this.logger.info(`${this.dryRun ? 'DRYRUN - ' : ''}Ran Actions`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +136,13 @@ export interface ICheck extends JoinCondition {
|
||||
* */
|
||||
name: string,
|
||||
description?: string,
|
||||
|
||||
/**
|
||||
* Use this option to override the `dryRun` setting for all of its `Actions`
|
||||
*
|
||||
* @default undefined
|
||||
* */
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface CheckOptions extends ICheck {
|
||||
|
||||
@@ -192,6 +192,13 @@ export interface ManagerOptions {
|
||||
apiLimitWarning?: number
|
||||
|
||||
caching?: false | SubredditCacheConfig
|
||||
|
||||
/**
|
||||
* Use this option to override the `dryRun` setting for all `Checks`
|
||||
*
|
||||
* @default undefined
|
||||
* */
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface ThresholdCriteria {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
|
||||
@@ -291,6 +291,11 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"dryRun": {
|
||||
"default": "undefined",
|
||||
"description": "Use this option to override the `dryRun` setting for all of its `Actions`",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of event (new submission or new comment) this check should be run against",
|
||||
"enum": [
|
||||
@@ -359,6 +364,11 @@
|
||||
"description": "Distinguish the comment after creation?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
@@ -456,6 +466,11 @@
|
||||
"description": "The text of the css class of the flair to apply",
|
||||
"type": "string"
|
||||
},
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
@@ -593,6 +608,11 @@
|
||||
"LockActionJson": {
|
||||
"description": "Lock the Activity",
|
||||
"properties": {
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
@@ -741,6 +761,11 @@
|
||||
"RemoveActionJson": {
|
||||
"description": "Remove the Activity",
|
||||
"properties": {
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
@@ -880,6 +905,11 @@
|
||||
"description": "The text of the report. If longer than 100 characters will be truncated to \"[content]...\"",
|
||||
"type": "string"
|
||||
},
|
||||
"dryRun": {
|
||||
"default": false,
|
||||
"description": "If `true` the Action will not make the API request to Reddit to perform its action.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
"enum": [
|
||||
@@ -974,6 +1004,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubredditCacheConfig": {
|
||||
"properties": {
|
||||
"authorTTL": {
|
||||
"description": "Amount of time, in milliseconds, author activities (Comments/Submission) should be cached",
|
||||
"type": "number"
|
||||
},
|
||||
"wikiTTL": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ThresholdCriteria": {
|
||||
"properties": {
|
||||
"condition": {
|
||||
@@ -1007,6 +1049,19 @@
|
||||
"description": "When Reddit API limit remaining reaches this number context bot will start warning on every poll interval",
|
||||
"type": "number"
|
||||
},
|
||||
"caching": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SubredditCacheConfig"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
false
|
||||
],
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checks": {
|
||||
"description": "A list of all the checks that should be run for a subreddit.\n\nChecks are split into two lists -- submission or comment -- based on kind and run independently.\n\nChecks in each list are run in the order found in the configuration.\n\nWhen a check \"passes\", and actions are performed, then all subsequent checks are skipped.",
|
||||
"items": {
|
||||
@@ -1015,6 +1070,11 @@
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"dryRun": {
|
||||
"default": "undefined",
|
||||
"description": "Use this option to override the `dryRun` setting for all `Checks`",
|
||||
"type": "boolean"
|
||||
},
|
||||
"heartbeatInterval": {
|
||||
"description": "If present, time in milliseconds between HEARTBEAT log statements with current api limit count. Nice to have to know things are still ticking if there is low activity",
|
||||
"type": "number"
|
||||
|
||||
@@ -32,6 +32,7 @@ export class Manager {
|
||||
heartbeatInterval?: number;
|
||||
lastHeartbeat = dayjs();
|
||||
apiLimitWarning: number;
|
||||
dryRun?: boolean;
|
||||
|
||||
displayLabel: string;
|
||||
currentLabels?: string[];
|
||||
@@ -55,13 +56,14 @@ export class Manager {
|
||||
|
||||
const configBuilder = new ConfigBuilder({logger: this.logger});
|
||||
const validJson = configBuilder.validateJson(sourceData);
|
||||
const {checks, ...managerOpts} = validJson;
|
||||
const {polling = {}, heartbeatInterval, apiLimitWarning = 250, caching} = managerOpts || {};
|
||||
const {checks, ...configManagerOpts} = validJson;
|
||||
const {polling = {}, heartbeatInterval, apiLimitWarning = 250, caching, dryRun} = configManagerOpts || {};
|
||||
this.pollOptions = {...polling, ...opts.polling};
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
this.apiLimitWarning = apiLimitWarning;
|
||||
this.subreddit = sub;
|
||||
this.client = client;
|
||||
this.dryRun = opts.dryRun || dryRun;
|
||||
|
||||
const cacheConfig = caching === false ? {enabled: false, logger: this.logger} : {
|
||||
...caching,
|
||||
@@ -74,10 +76,11 @@ export class Manager {
|
||||
const subChecks: Array<SubmissionCheck> = [];
|
||||
const structuredChecks = configBuilder.parseToStructured(validJson);
|
||||
for (const jCheck of structuredChecks) {
|
||||
const checkConfig = {...jCheck, dryRun: this.dryRun || jCheck.dryRun, logger: this.logger, subredditName: sub.display_name};
|
||||
if (jCheck.kind === 'comment') {
|
||||
commentChecks.push(new CommentCheck({...jCheck, logger: this.logger, subredditName: sub.display_name}));
|
||||
commentChecks.push(new CommentCheck(checkConfig));
|
||||
} else if (jCheck.kind === 'submission') {
|
||||
subChecks.push(new SubmissionCheck({...jCheck, logger: this.logger, subredditName: sub.display_name}));
|
||||
subChecks.push(new SubmissionCheck(checkConfig));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export const getOptions = () => {
|
||||
options.push(new Option('--snooDebug', 'Set Snoowrap to debug').default(undefined, 'process.env.SNOO_DEBUG'));
|
||||
options.push(new Option('--authorTTL <ms>', 'Set the TTL (ms) for the Author Activities shared cache').default(process.env.AUTHOR_TTL || 10000, 'process.env.AUTHOR_TTL || 10000'));
|
||||
options.push(new Option('--disableCache', 'Disable caching for all subreddits'));
|
||||
options.push(new Option('--dryRun', 'Set dryRun=true for all checks/actions on all subreddits (overrides any existing)').default(undefined, 'process.env.DRYRUN'));
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user