mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-04-19 03:00:07 -04:00
More annotation improvements
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"build": "tsc",
|
||||
"start": "node server.js",
|
||||
"guard": "ts-auto-guard src/JsonConfig.ts",
|
||||
"schema": "typescript-json-schema tsconfig.json JSONConfig --out src/Schema/schema.json --required --tsNodeRegister",
|
||||
"schema": "typescript-json-schema tsconfig.json JSONConfig --out src/Schema/schema.json --required --tsNodeRegister --refs --propOrder",
|
||||
"schemaNotWorking": "./node_modules/.bin/ts-json-schema-generator -f tsconfig.json -p src/JsonConfig.ts -t JSONConfig --out src/Schema/vegaSchema.json"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -3,6 +3,7 @@ import Snoowrap, {Comment} from "snoowrap";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import {renderContent} from "../Utils/SnoowrapUtils";
|
||||
import {RichContent} from "../Common/interfaces";
|
||||
|
||||
export const WIKI_DESCRIM = 'wiki:';
|
||||
|
||||
@@ -58,20 +59,27 @@ export class CommentAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentActionConfig {
|
||||
export interface CommentActionConfig extends RichContent {
|
||||
/**
|
||||
* Content is interpreted as reddit-flavored Markdown. If value starts with 'wiki:' then the proceeding value will be use to get a wiki page
|
||||
* EX wiki:botconfig/mybot ==> try to get http://reddit.com/mySubredditExample/wiki/botconfig/mybot
|
||||
* */
|
||||
content: string,
|
||||
* Lock the comment after creation?
|
||||
* */
|
||||
lock?: boolean,
|
||||
/**
|
||||
* Stick the comment after creation?
|
||||
* */
|
||||
sticky?: boolean,
|
||||
/**
|
||||
* Distinguish the comment after creation?
|
||||
* */
|
||||
distinguish?: boolean,
|
||||
}
|
||||
|
||||
export interface CommentActionOptions extends CommentActionConfig,ActionOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to the Activity. For a submission the reply will be a top-level comment.
|
||||
* */
|
||||
export interface CommentActionJSONConfig extends CommentActionConfig, ActionJSONConfig {
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ export interface LockActionConfig extends ActionConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the Activity
|
||||
* */
|
||||
export interface LockActionJSONConfig extends LockActionConfig, ActionJSONConfig {
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface RemoveActionConfig extends ActionConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the Activity
|
||||
* */
|
||||
export interface RemoveActionJSONConfig extends RemoveActionConfig, ActionJSONConfig {
|
||||
|
||||
}
|
||||
|
||||
@@ -17,13 +17,19 @@ export class ReportAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReportActionConfig{
|
||||
export interface ReportActionConfig {
|
||||
/**
|
||||
* The text of the report
|
||||
* */
|
||||
content: string,
|
||||
}
|
||||
|
||||
export interface ReportActionOptions extends ReportActionConfig,ActionOptions {
|
||||
export interface ReportActionOptions extends ReportActionConfig, ActionOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the Activity
|
||||
* */
|
||||
export interface ReportActionJSONConfig extends ReportActionConfig, ActionJSONConfig {
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ export class FlairAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
export interface FlairActionOptions extends SubmissionActionConfig {
|
||||
/**
|
||||
* The text of the flair to apply
|
||||
@@ -36,7 +40,7 @@ export interface FlairActionOptions extends SubmissionActionConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* text and css cannot both be empty
|
||||
* Flair the Submission
|
||||
* */
|
||||
export interface FlairActionJSONConfig extends FlairActionOptions, ActionJSONConfig {
|
||||
|
||||
|
||||
@@ -17,12 +17,13 @@ import {AuthorRuleJSONConfig} from "../Rule/AuthorRule";
|
||||
import {ReportActionJSONConfig} from "../Action/ReportAction";
|
||||
import {LockActionJSONConfig} from "../Action/LockAction";
|
||||
import {RemoveActionJSONConfig} from "../Action/RemoveAction";
|
||||
import {JoinCondition, JoinOperands} from "../Common/interfaces";
|
||||
|
||||
export class Check implements ICheck {
|
||||
actions: Action[] = [];
|
||||
description?: string;
|
||||
name: string;
|
||||
ruleJoin: "OR" | "AND";
|
||||
condition: JoinOperands;
|
||||
rules: Array<RuleSet | Rule> = [];
|
||||
logger: Logger;
|
||||
|
||||
@@ -30,9 +31,9 @@ export class Check implements ICheck {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
ruleJoin = 'AND',
|
||||
rules,
|
||||
actions,
|
||||
condition = 'AND',
|
||||
rules = [],
|
||||
actions = [],
|
||||
} = options;
|
||||
|
||||
if (options.logger !== undefined) {
|
||||
@@ -44,7 +45,7 @@ export class Check implements ICheck {
|
||||
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.ruleJoin = ruleJoin;
|
||||
this.condition = condition;
|
||||
for (const r of rules) {
|
||||
if (r instanceof Rule || r instanceof RuleSet) {
|
||||
this.rules.push(r);
|
||||
@@ -82,10 +83,10 @@ export class Check implements ICheck {
|
||||
}
|
||||
runOne = true;
|
||||
if (passed) {
|
||||
if (this.ruleJoin === 'OR') {
|
||||
if (this.condition === 'OR') {
|
||||
return [true, allResults];
|
||||
}
|
||||
} else if (this.ruleJoin === 'AND') {
|
||||
} else if (this.condition === 'AND') {
|
||||
return [false, allResults];
|
||||
}
|
||||
}
|
||||
@@ -102,16 +103,12 @@ export class Check implements ICheck {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICheck {
|
||||
export interface ICheck extends JoinCondition {
|
||||
/**
|
||||
* A friendly name for this check (highly recommended) -- EX "repeatCrosspostReport"
|
||||
* */
|
||||
name: string,
|
||||
description?: string,
|
||||
/**
|
||||
* Under what condition should a check's rules be "successful"? If 'OR' then ANY triggered rule will cause actions to run. If 'AND' then ALL rules must be triggered for actions to run.
|
||||
* */
|
||||
ruleJoin?: 'OR' | 'AND',
|
||||
}
|
||||
|
||||
export interface CheckOptions extends ICheck {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {DurationUnitsObjectType} from "dayjs/plugin/duration";
|
||||
|
||||
/**
|
||||
* An ISO 8601 Duration
|
||||
* @pattern ^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$
|
||||
* */
|
||||
export type ISO8601 = string;
|
||||
export type ActivityWindowType = ISO8601 | number | ActivityWindowCriteria;
|
||||
export type ActivityWindowType = Duration | number | ActivityWindowCriteria;
|
||||
export type Duration = ISO8601 | DurationObject;
|
||||
|
||||
/**
|
||||
* If both properties are defined then the first criteria met will be used IE if # of activities = count before duration is reached then count will be used, or vice versa
|
||||
@@ -18,15 +17,22 @@ export interface ActivityWindowCriteria {
|
||||
* */
|
||||
count?: number,
|
||||
/**
|
||||
* An ISO 8601 duration or Day.js duration object
|
||||
* An ISO 8601 duration or Day.js duration object.
|
||||
*
|
||||
* The duration will be subtracted from the time when the rule is run to create a time range like this:
|
||||
*
|
||||
* endTime = NOW <----> startTime = (NOW - duration)
|
||||
*
|
||||
* EX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM
|
||||
* @examples ["PT1M", {"minutes": 15}]
|
||||
* */
|
||||
duration?: ISO8601 | DurationObject
|
||||
duration?: Duration
|
||||
}
|
||||
|
||||
/**
|
||||
* A Day.js duration object
|
||||
* @see https://day.js.org/docs/en/durations/creating
|
||||
*
|
||||
* https://day.js.org/docs/en/durations/creating
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
@@ -63,10 +69,106 @@ export const windowExample: ActivityWindowType[] = [
|
||||
|
||||
export interface ActivityWindow {
|
||||
/**
|
||||
* Criteria for defining what set of activities should be considered. See ActivityWindowCriteria for descriptions of what different data types will do
|
||||
* //@examples require('./interfaces.ts').windowExample
|
||||
* Criteria for defining what set of activities should be considered.
|
||||
*
|
||||
* The value of this property may be either count OR duration -- to use both write it as an ActivityWindowCriteria
|
||||
*
|
||||
* See ActivityWindowCriteria for descriptions of what count/duration do
|
||||
* @examples require('./interfaces.ts').windowExample
|
||||
* @default 15
|
||||
*/
|
||||
window?: ActivityWindowType,
|
||||
}
|
||||
|
||||
// export type AtLeastOne<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
|
||||
export interface ReferenceSubmission {
|
||||
/**
|
||||
* If activity is a Submission and is a link (not self-post) then only look at Submissions that contain this link, otherwise consider all activities.
|
||||
* @default true
|
||||
* */
|
||||
useSubmissionAsReference?: boolean,
|
||||
}
|
||||
|
||||
export interface RichContent {
|
||||
/**
|
||||
* The Content to submit for this Action. Content is interpreted as reddit-flavored Markdown.
|
||||
*
|
||||
* If value starts with 'wiki:' then the proceeding value will be used to get a wiki page
|
||||
*
|
||||
* EX "wiki:botconfig/mybot" tries to get https://reddit.com/mySubredditExample/wiki/botconfig/mybot
|
||||
*
|
||||
* EX "this is plain text"
|
||||
*
|
||||
* EX "this is **bold** markdown text"
|
||||
*
|
||||
* @examples ["this is plain text", "this is **bold** markdown text", "wiki:botconfig/acomment" ]
|
||||
* */
|
||||
content: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of subreddits (case-insensitive) to look for. Do not include "r/" prefix.
|
||||
*
|
||||
* EX to match against /r/mealtimevideos and /r/askscience use ["mealtimevideos","askscience"]
|
||||
* @examples ["mealtimevideos","askscience"]
|
||||
* @minItems 1
|
||||
* */
|
||||
export type SubredditList = string[];
|
||||
|
||||
export interface SubredditCriteria {
|
||||
subreddits: SubredditList
|
||||
}
|
||||
|
||||
export type JoinOperands = 'OR' | 'AND';
|
||||
|
||||
export interface JoinCondition {
|
||||
/**
|
||||
* Under what condition should a set of rules be considered "successful"?
|
||||
*
|
||||
* If "OR" then ANY triggered rule results in success.
|
||||
*
|
||||
* If "AND" then ALL rules must be triggered to result in success.
|
||||
*
|
||||
* @default "AND"
|
||||
* */
|
||||
condition?: JoinOperands,
|
||||
}
|
||||
|
||||
/**
|
||||
* You may specify polling options independently for submissions/comments
|
||||
* */
|
||||
export interface PollingOptions {
|
||||
/**
|
||||
* Polling options for submission events
|
||||
* */
|
||||
submissions?: {
|
||||
/**
|
||||
* The number of submissions to pull from /r/subreddit/new on every request
|
||||
* @default 10
|
||||
* */
|
||||
limit?: number,
|
||||
/**
|
||||
* Amount of time to wait between requests to /r/subreddit/new
|
||||
*
|
||||
* @format milliseconds
|
||||
* @default 10000
|
||||
* */
|
||||
interval?: number,
|
||||
},
|
||||
/**
|
||||
* Polling options for comment events
|
||||
* */
|
||||
comments?: {
|
||||
/**
|
||||
* The number of new comments to pull on every request
|
||||
* @default 10
|
||||
* */
|
||||
limit?: number,
|
||||
/**
|
||||
* Amount of time to wait between requests for new comments
|
||||
*
|
||||
* @format milliseconds
|
||||
* @default 10000
|
||||
* */
|
||||
interval?: number,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import {CheckJSONConfig} from "./Check";
|
||||
import {PollingOptions} from "./Common/interfaces";
|
||||
|
||||
export interface JSONConfig {
|
||||
/**
|
||||
* A list of all the checks that should be run for a subreddit. Checks are split into two lists -- submission or comment -- based on kind and run independently. Checks in each list are run in the order found in the configuration. When a check "passes" and actions are performed any subsequent checks are skipped.
|
||||
* A list of all the checks that should be run for a subreddit.
|
||||
*
|
||||
* Checks are split into two lists -- submission or comment -- based on kind and run independently.
|
||||
*
|
||||
* Checks in each list are run in the order found in the configuration.
|
||||
*
|
||||
* When a check "passes", and actions are performed, then all subsequent checks are skipped.
|
||||
* @minItems 1
|
||||
* */
|
||||
checks: CheckJSONConfig[]
|
||||
polling?: PollingOptions
|
||||
}
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
import {Author, AuthorOptions, IAuthor, Rule, RuleJSONConfig, RuleOptions, RuleResult} from "./index";
|
||||
import {Author, AuthorOptions, AuthorCriteria, Rule, RuleJSONConfig, RuleOptions, RuleResult} from "./index";
|
||||
import {Comment} from "snoowrap";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import {testAuthorCriteria} from "../Utils/SnoowrapUtils";
|
||||
|
||||
export interface AuthorRuleConfig extends AuthorOptions {
|
||||
include: IAuthor[];
|
||||
exclude: IAuthor[];
|
||||
/**
|
||||
* Checks the author of the Activity against AuthorCriteria. This differs from a Rule's AuthorOptions as this is a full Rule and will only pass/fail, not skip.
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
export interface AuthorRuleConfig {
|
||||
/**
|
||||
* Will "pass" if any set of AuthorCriteria passes
|
||||
* */
|
||||
include: AuthorCriteria[];
|
||||
/**
|
||||
* Only runs if include is not present. Will "pass" if any of set of the AuthorCriteria does not pass
|
||||
* */
|
||||
exclude: AuthorCriteria[];
|
||||
}
|
||||
|
||||
export interface AuthorRuleOptions extends AuthorRuleConfig, RuleOptions {
|
||||
@@ -13,12 +24,12 @@ export interface AuthorRuleOptions extends AuthorRuleConfig, RuleOptions {
|
||||
}
|
||||
|
||||
export interface AuthorRuleJSONConfig extends AuthorRuleConfig, RuleJSONConfig {
|
||||
|
||||
kind: 'author'
|
||||
}
|
||||
|
||||
export class AuthorRule extends Rule {
|
||||
include: IAuthor[] = [];
|
||||
exclude: IAuthor[] = [];
|
||||
include: AuthorCriteria[] = [];
|
||||
exclude: AuthorCriteria[] = [];
|
||||
|
||||
constructor(options: AuthorRuleOptions) {
|
||||
super(options);
|
||||
|
||||
@@ -3,25 +3,31 @@ import {Comment, VoteableContent} from "snoowrap";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import {getAuthorActivities, getAuthorComments, getAuthorSubmissions} from "../Utils/SnoowrapUtils";
|
||||
import {parseUsableLinkIdentifier} from "../util";
|
||||
import {ActivityWindow, ActivityWindowCriteria, ActivityWindowType} from "../Common/interfaces";
|
||||
import {
|
||||
ActivityWindow,
|
||||
ActivityWindowCriteria,
|
||||
ActivityWindowType,
|
||||
ReferenceSubmission,
|
||||
SubredditCriteria
|
||||
} from "../Common/interfaces";
|
||||
|
||||
const parseLink = parseUsableLinkIdentifier();
|
||||
|
||||
export class RecentActivityRule extends Rule {
|
||||
window: ActivityWindowType;
|
||||
thresholds: SubThreshold[];
|
||||
usePostAsReference: boolean;
|
||||
useSubmissionAsReference: boolean;
|
||||
lookAt?: 'comments' | 'submissions';
|
||||
|
||||
constructor(options: RecentActivityRuleOptions) {
|
||||
super(options);
|
||||
const {
|
||||
window = 15,
|
||||
usePostAsReference = true,
|
||||
useSubmissionAsReference = true,
|
||||
lookAt,
|
||||
} = options || {};
|
||||
this.lookAt = lookAt;
|
||||
this.usePostAsReference = usePostAsReference;
|
||||
this.useSubmissionAsReference = useSubmissionAsReference;
|
||||
this.window = window;
|
||||
this.thresholds = options.thresholds;
|
||||
}
|
||||
@@ -34,7 +40,7 @@ export class RecentActivityRule extends Rule {
|
||||
return {
|
||||
window: this.window,
|
||||
thresholds: this.thresholds,
|
||||
usePostAsReference: this.usePostAsReference,
|
||||
useSubmissionAsReference: this.useSubmissionAsReference,
|
||||
lookAt: this.lookAt
|
||||
}
|
||||
}
|
||||
@@ -56,7 +62,7 @@ export class RecentActivityRule extends Rule {
|
||||
|
||||
|
||||
let viableActivity = activities;
|
||||
if (this.usePostAsReference) {
|
||||
if (this.useSubmissionAsReference) {
|
||||
if (!(item instanceof Submission)) {
|
||||
this.logger.debug('Cannot use post as reference because triggered item is not a Submission');
|
||||
} else if (item.is_self) {
|
||||
@@ -101,43 +107,35 @@ export class RecentActivityRule extends Rule {
|
||||
}
|
||||
}
|
||||
|
||||
export interface SubThreshold {
|
||||
/**
|
||||
* A list of subreddits (case-insensitive) to look for
|
||||
* @minItems 1
|
||||
* */
|
||||
subreddits: string[],
|
||||
export interface SubThreshold extends SubredditCriteria {
|
||||
/**
|
||||
* The number of activities in each subreddit from the list that will trigger this rule
|
||||
* @default 1
|
||||
* @minimum 1
|
||||
* */
|
||||
count?: number,
|
||||
}
|
||||
|
||||
interface RecentActivityConfig extends ActivityWindow {
|
||||
/**
|
||||
* If activity is a Submission and is a link (not self-post) then only look at Submissions that contain this link, otherwise consider all activities.
|
||||
* */
|
||||
usePostAsReference?: boolean,
|
||||
interface RecentActivityConfig extends ActivityWindow, ReferenceSubmission {
|
||||
/**
|
||||
* If present restricts the activities that are considered for count from SubThreshold
|
||||
* */
|
||||
lookAt?: 'comments' | 'submissions',
|
||||
/**
|
||||
* A list of subreddits/count criteria that may trigger this rule. ANY SubThreshold will trigger this rule.
|
||||
* @minItems 1
|
||||
* */
|
||||
thresholds: SubThreshold[],
|
||||
|
||||
/**
|
||||
* @default 15
|
||||
* */
|
||||
window: ActivityWindowType;
|
||||
}
|
||||
|
||||
export interface RecentActivityRuleOptions extends RecentActivityConfig, RuleOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a user's history for any Activity (Submission/Comment) in the subreddits specified in thresholds
|
||||
* */
|
||||
export interface RecentActivityRuleJSONConfig extends RecentActivityConfig, RuleJSONConfig {
|
||||
|
||||
kind: 'recentActivity'
|
||||
}
|
||||
|
||||
export default RecentActivityRule;
|
||||
|
||||
@@ -7,20 +7,22 @@ import {RepeatSubmissionJSONConfig} from "./SubmissionRule/RepeatSubmissionRule"
|
||||
import {createLabelledLogger, determineNewResults, findResultByPremise, loggerMetaShuffle} from "../util";
|
||||
import {Logger} from "winston";
|
||||
import {AuthorRuleJSONConfig} from "./AuthorRule";
|
||||
import {JoinCondition, JoinOperands} from "../Common/interfaces";
|
||||
|
||||
export class RuleSet implements IRuleSet, Triggerable {
|
||||
rules: Rule[] = [];
|
||||
condition: 'OR' | 'AND';
|
||||
condition: JoinOperands;
|
||||
logger: Logger;
|
||||
|
||||
constructor(options: RuleSetOptions) {
|
||||
if (options.logger !== undefined) {
|
||||
this.logger = options.logger.child(loggerMetaShuffle(options.logger, 'Rule Set'));
|
||||
const {logger, condition = 'AND', rules = []} = options;
|
||||
if (logger !== undefined) {
|
||||
this.logger = logger.child(loggerMetaShuffle(logger, 'Rule Set'));
|
||||
} else {
|
||||
this.logger = createLabelledLogger('Rule Set');
|
||||
}
|
||||
this.condition = options.condition;
|
||||
for (const r of options.rules) {
|
||||
this.condition = condition;
|
||||
for (const r of rules) {
|
||||
if (r instanceof Rule) {
|
||||
this.rules.push(r);
|
||||
} else if (isRuleConfig(r)) {
|
||||
@@ -60,11 +62,7 @@ export class RuleSet implements IRuleSet, Triggerable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRuleSet {
|
||||
/**
|
||||
* Under what condition should a RuleSet's rules be "successful"? If 'OR' then ANY triggered rule result in a true outcome. If 'AND' then ALL rules must be triggered for the result to be true.
|
||||
* */
|
||||
condition: 'OR' | 'AND',
|
||||
export interface IRuleSet extends JoinCondition {
|
||||
/**
|
||||
* @minItems 1
|
||||
* */
|
||||
@@ -81,5 +79,8 @@ export interface RuleSetOptions extends IRuleSet {
|
||||
* @see {isRuleSetConfig} ts-auto-guard:type-guard
|
||||
* */
|
||||
export interface RuleSetJSONConfig extends IRuleSet {
|
||||
/**
|
||||
* @minItems 1
|
||||
* */
|
||||
rules: Array<RecentActivityRuleJSONConfig | RepeatSubmissionJSONConfig | AuthorRuleJSONConfig>
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ import {Rule, RuleOptions, RulePremise, RuleResult} from "../index";
|
||||
import {Submission} from "snoowrap";
|
||||
import {getAuthorSubmissions} from "../../Utils/SnoowrapUtils";
|
||||
import {groupBy, parseUsableLinkIdentifier as linkParser} from "../../util";
|
||||
import {ActivityWindow, ActivityWindowType, ReferenceSubmission} from "../../Common/interfaces";
|
||||
|
||||
const groupByUrl = groupBy(['urlIdentifier']);
|
||||
const parseUsableLinkIdentifier = linkParser()
|
||||
|
||||
export class RepeatSubmissionRule extends SubmissionRule {
|
||||
threshold: number;
|
||||
window: string | number;
|
||||
window: ActivityWindowType;
|
||||
gapAllowance?: number;
|
||||
usePostAsReference: boolean;
|
||||
useSubmissionAsReference: boolean;
|
||||
include: string[];
|
||||
exclude: string[];
|
||||
|
||||
@@ -21,14 +22,14 @@ export class RepeatSubmissionRule extends SubmissionRule {
|
||||
threshold = 5,
|
||||
window = 15,
|
||||
gapAllowance,
|
||||
usePostAsReference = true,
|
||||
useSubmissionAsReference = true,
|
||||
include = [],
|
||||
exclude = []
|
||||
} = options;
|
||||
this.threshold = threshold;
|
||||
this.window = window;
|
||||
this.gapAllowance = gapAllowance;
|
||||
this.usePostAsReference = usePostAsReference;
|
||||
this.useSubmissionAsReference = useSubmissionAsReference;
|
||||
this.include = include;
|
||||
this.exclude = exclude;
|
||||
}
|
||||
@@ -42,7 +43,7 @@ export class RepeatSubmissionRule extends SubmissionRule {
|
||||
threshold: this.threshold,
|
||||
window: this.window,
|
||||
gapAllowance: this.gapAllowance,
|
||||
usePostAsReference: this.usePostAsReference,
|
||||
useSubmissionAsReference: this.useSubmissionAsReference,
|
||||
include: this.include,
|
||||
exclude: this.exclude,
|
||||
}
|
||||
@@ -50,7 +51,7 @@ export class RepeatSubmissionRule extends SubmissionRule {
|
||||
|
||||
async process(item: Submission): Promise<[boolean, RuleResult[]]> {
|
||||
const referenceUrl = await item.url;
|
||||
if (referenceUrl === undefined && this.usePostAsReference) {
|
||||
if (referenceUrl === undefined && this.useSubmissionAsReference) {
|
||||
throw new Error(`Cannot run Rule ${this.name} because submission is not a link`);
|
||||
}
|
||||
const submissions = await getAuthorSubmissions(item.author, {window: this.window});
|
||||
@@ -97,7 +98,7 @@ export class RepeatSubmissionRule extends SubmissionRule {
|
||||
urlIdentifier: parseUsableLinkIdentifier(x.url)
|
||||
})));
|
||||
let groupsToCheck = [];
|
||||
if (this.usePostAsReference) {
|
||||
if (this.useSubmissionAsReference) {
|
||||
const identifier = parseUsableLinkIdentifier(referenceUrl);
|
||||
const {[identifier as string]: refGroup = []} = groupedPosts;
|
||||
groupsToCheck.push(refGroup);
|
||||
@@ -116,24 +117,46 @@ export class RepeatSubmissionRule extends SubmissionRule {
|
||||
}
|
||||
}
|
||||
|
||||
interface RepeatSubmissionConfig {
|
||||
threshold: number,
|
||||
window?: string | number,
|
||||
interface RepeatSubmissionConfig extends ActivityWindow, ReferenceSubmission {
|
||||
/**
|
||||
* The number of repeat submissions that will trigger the rule
|
||||
* @default 5
|
||||
* */
|
||||
threshold?: number,
|
||||
/**
|
||||
* The number of allowed non-identical Submissions between identical Submissions that can be ignored when checking against the threshold value
|
||||
* */
|
||||
gapAllowance?: number,
|
||||
/**
|
||||
* If activity is a Submission and is a link (not self-post) then only look at Submissions that contain this link, otherwise consider all activities.
|
||||
* Only include Submissions from this list of Subreddits.
|
||||
*
|
||||
* A list of subreddits (case-insensitive) to look for. Do not include "r/" prefix.
|
||||
*
|
||||
* EX to match against /r/mealtimevideos and /r/askscience use ["mealtimevideos","askscience"]
|
||||
* @examples ["mealtimevideos","askscience"]
|
||||
* @minItems 1
|
||||
* */
|
||||
usePostAsReference?: boolean,
|
||||
include?: string[],
|
||||
/**
|
||||
* Do not include Submissions from this list of Subreddits.
|
||||
*
|
||||
* A list of subreddits (case-insensitive) to look for. Do not include "r/" prefix.
|
||||
*
|
||||
* EX to match against /r/mealtimevideos and /r/askscience use ["mealtimevideos","askscience"]
|
||||
* @examples ["mealtimevideos","askscience"]
|
||||
* @minItems 1
|
||||
* */
|
||||
exclude?: string[],
|
||||
}
|
||||
|
||||
export interface RepeatSubmissionOptions extends RepeatSubmissionConfig, RuleOptions {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a user's history for Submissions with identical content
|
||||
* */
|
||||
export interface RepeatSubmissionJSONConfig extends RepeatSubmissionConfig, SubmissionRuleJSONConfig {
|
||||
|
||||
kind: 'repeatSubmission'
|
||||
}
|
||||
|
||||
export default RepeatSubmissionRule;
|
||||
|
||||
@@ -117,13 +117,13 @@ export abstract class Rule implements IRule, Triggerable {
|
||||
}
|
||||
}
|
||||
|
||||
export class Author implements IAuthor {
|
||||
export class Author implements AuthorCriteria {
|
||||
name?: string[];
|
||||
flairCssClass?: string[];
|
||||
flairText?: string[];
|
||||
isMod?: boolean;
|
||||
|
||||
constructor(options: IAuthor) {
|
||||
constructor(options: AuthorCriteria) {
|
||||
this.name = options.name;
|
||||
this.flairCssClass = options.flairCssClass;
|
||||
this.flairText = options.flairText;
|
||||
@@ -132,25 +132,36 @@ export class Author implements IAuthor {
|
||||
}
|
||||
|
||||
/**
|
||||
* If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note that when used on AuthorRule this becomes pass/fail (no skip)
|
||||
* If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
export interface AuthorOptions {
|
||||
/**
|
||||
* Only runs if include is not present. Will "pass" if any of set of the Author criteria do not pass
|
||||
* Will "pass" if any set of AuthorCriteria passes
|
||||
* */
|
||||
exclude?: IAuthor[];
|
||||
include?: AuthorCriteria[];
|
||||
/**
|
||||
* Will "pass" if any set of the Author criteria passes
|
||||
* Only runs if include is not present. Will "pass" if any of set of the AuthorCriteria does not pass
|
||||
* */
|
||||
include?: IAuthor[];
|
||||
exclude?: AuthorCriteria[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Criteria with which to test against the author of a submission/comment. The outcome of the test is based on 1. any list criteria matching and then 2. all present criteria passing
|
||||
* Criteria with which to test against the author of an Activity. The outcome of the test is based on:
|
||||
*
|
||||
* 1. All present properties passing and
|
||||
* 2. If a property is a list then any value from the list matching
|
||||
*
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
export interface IAuthor {
|
||||
export interface AuthorCriteria {
|
||||
/**
|
||||
* A list of reddit usernames (case-insensitive) to match against
|
||||
* A list of reddit usernames (case-insensitive) to match against. Do not include the "u/" prefix
|
||||
*
|
||||
* EX to match against /u/FoxxMD and /u/AnotherUser use ["FoxxMD","AnotherUser"]
|
||||
* @examples ["FoxxMD","AnotherUser"]
|
||||
* */
|
||||
name?: string[],
|
||||
/**
|
||||
@@ -173,7 +184,7 @@ export interface IRule {
|
||||
* */
|
||||
name?: string
|
||||
/**
|
||||
* If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note this is NOT the same as AuthorRule.
|
||||
* If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.
|
||||
* */
|
||||
authors?: AuthorOptions
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "An ISO 8601 duration or Day.js duration object",
|
||||
"description": "An ISO 8601 duration or Day.js duration object.\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
|
||||
"examples": [
|
||||
"PT1M",
|
||||
{
|
||||
@@ -28,54 +28,107 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"count",
|
||||
"duration"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AuthorOptions": {
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note that when used on AuthorRule this becomes pass/fail (no skip)",
|
||||
"AuthorCriteria": {
|
||||
"additionalProperties": false,
|
||||
"description": "Criteria with which to test against the author of an Activity. The outcome of the test is based on:\n\n1. All present properties passing and\n2. If a property is a list then any value from the list matching",
|
||||
"minProperties": 1,
|
||||
"properties": {
|
||||
"exclude": {
|
||||
"description": "Only runs if include is not present. Will \"pass\" if any of set of the Author criteria do not pass",
|
||||
"flairCssClass": {
|
||||
"description": "A list of (user) flair css class values from the subreddit to match against",
|
||||
"items": {
|
||||
"$ref": "#/definitions/IAuthor"
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"include": {
|
||||
"description": "Will \"pass\" if any set of the Author criteria passes",
|
||||
"flairText": {
|
||||
"description": "A list of (user) flair text values from the subreddit to match against",
|
||||
"items": {
|
||||
"$ref": "#/definitions/IAuthor"
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"isMod": {
|
||||
"description": "Is the author a moderator?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "A list of reddit usernames (case-insensitive) to match against. Do not include the \"u/\" prefix\n\n EX to match against /u/FoxxMD and /u/AnotherUser use [\"FoxxMD\",\"AnotherUser\"]",
|
||||
"examples": [
|
||||
"FoxxMD",
|
||||
"AnotherUser"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"name",
|
||||
"flairCssClass",
|
||||
"flairText",
|
||||
"isMod"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AuthorOptions": {
|
||||
"additionalProperties": false,
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.",
|
||||
"minProperties": 1,
|
||||
"properties": {
|
||||
"exclude": {
|
||||
"description": "Only runs if include is not present. Will \"pass\" if any of set of the AuthorCriteria does not pass",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AuthorCriteria"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"include": {
|
||||
"description": "Will \"pass\" if any set of AuthorCriteria passes",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AuthorCriteria"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"include",
|
||||
"exclude"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AuthorRuleJSONConfig": {
|
||||
"properties": {
|
||||
"authors": {
|
||||
"$ref": "#/definitions/AuthorOptions",
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note this is NOT the same as AuthorRule."
|
||||
"additionalProperties": false,
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.",
|
||||
"minProperties": 1
|
||||
},
|
||||
"exclude": {
|
||||
"description": "Only runs if include is not present. Will \"pass\" if any of set of the Author criteria do not pass",
|
||||
"description": "Only runs if include is not present. Will \"pass\" if any of set of the AuthorCriteria does not pass",
|
||||
"items": {
|
||||
"$ref": "#/definitions/IAuthor"
|
||||
"$ref": "#/definitions/AuthorCriteria"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"include": {
|
||||
"description": "Will \"pass\" if any set of the Author criteria passes",
|
||||
"description": "Will \"pass\" if any set of AuthorCriteria passes",
|
||||
"items": {
|
||||
"$ref": "#/definitions/IAuthor"
|
||||
"$ref": "#/definitions/AuthorCriteria"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The kind of rule to run",
|
||||
"enum": [
|
||||
"author",
|
||||
"recentActivity",
|
||||
"repeatSubmission"
|
||||
"author"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -84,6 +137,13 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"kind",
|
||||
"include",
|
||||
"exclude",
|
||||
"name",
|
||||
"authors"
|
||||
],
|
||||
"required": [
|
||||
"exclude",
|
||||
"include",
|
||||
@@ -118,6 +178,15 @@
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"condition": {
|
||||
"default": "AND",
|
||||
"description": "Under what condition should a set of rules be considered \"successful\"?\n\nIf \"OR\" then ANY triggered rule results in success.\n\nIf \"AND\" then ALL rules must be triggered to result in success.",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -133,14 +202,6 @@
|
||||
"description": "A friendly name for this check (highly recommended) -- EX \"repeatCrosspostReport\"",
|
||||
"type": "string"
|
||||
},
|
||||
"ruleJoin": {
|
||||
"description": "Under what condition should a check's rules be \"successful\"? If 'OR' then ANY triggered rule will cause actions to run. If 'AND' then ALL rules must be triggered for actions to run.",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"rules": {
|
||||
"description": "Rules are run in the order found in configuration. Can be Rules or RuleSets",
|
||||
"items": {
|
||||
@@ -163,6 +224,14 @@
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"kind",
|
||||
"rules",
|
||||
"actions",
|
||||
"name",
|
||||
"description",
|
||||
"condition"
|
||||
],
|
||||
"required": [
|
||||
"actions",
|
||||
"kind",
|
||||
@@ -172,12 +241,19 @@
|
||||
"type": "object"
|
||||
},
|
||||
"CommentActionJSONConfig": {
|
||||
"description": "Reply to the Activity. For a submission the reply will be a top-level comment.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content is interpreted as reddit-flavored Markdown. If value starts with 'wiki:' then the proceeding value will be use to get a wiki page\nEX wiki:botconfig/mybot ==> try to get http://reddit.com/mySubredditExample/wiki/botconfig/mybot",
|
||||
"description": "The Content to submit for this Action. Content is interpreted as reddit-flavored Markdown.\n\nIf value starts with 'wiki:' then the proceeding value will be used to get a wiki page\n\nEX \"wiki:botconfig/mybot\" tries to get https://reddit.com/mySubredditExample/wiki/botconfig/mybot\n\nEX \"this is plain text\"\n\nEX \"this is **bold** markdown text\"",
|
||||
"examples": [
|
||||
"this is plain text",
|
||||
"this is **bold** markdown text",
|
||||
"wiki:botconfig/acomment"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"distinguish": {
|
||||
"description": "Distinguish the comment after creation?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
@@ -192,6 +268,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"lock": {
|
||||
"description": "Lock the comment after creation?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
@@ -199,9 +276,18 @@
|
||||
"type": "string"
|
||||
},
|
||||
"sticky": {
|
||||
"description": "Stick the comment after creation?",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"lock",
|
||||
"sticky",
|
||||
"distinguish",
|
||||
"content",
|
||||
"kind",
|
||||
"name"
|
||||
],
|
||||
"required": [
|
||||
"content",
|
||||
"kind"
|
||||
@@ -210,7 +296,7 @@
|
||||
},
|
||||
"DurationObject": {
|
||||
"additionalProperties": false,
|
||||
"description": "A Day.js duration object",
|
||||
"description": "A Day.js duration object\n\nhttps://day.js.org/docs/en/durations/creating",
|
||||
"minProperties": 1,
|
||||
"properties": {
|
||||
"days": {
|
||||
@@ -235,10 +321,19 @@
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"seconds",
|
||||
"minutes",
|
||||
"hours",
|
||||
"days",
|
||||
"weeks",
|
||||
"months",
|
||||
"years"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FlairActionJSONConfig": {
|
||||
"description": "text and css cannot both be empty",
|
||||
"description": "Flair the Submission",
|
||||
"properties": {
|
||||
"css": {
|
||||
"description": "The text of the css class of the flair to apply",
|
||||
@@ -264,43 +359,19 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"text",
|
||||
"css",
|
||||
"name",
|
||||
"kind"
|
||||
],
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"IAuthor": {
|
||||
"description": "Criteria with which to test against the author of a submission/comment. The outcome of the test is based on 1. any list criteria matching and then 2. all present criteria passing",
|
||||
"properties": {
|
||||
"flairCssClass": {
|
||||
"description": "A list of (user) flair css class values from the subreddit to match against",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"flairText": {
|
||||
"description": "A list of (user) flair text values from the subreddit to match against",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"isMod": {
|
||||
"description": "Is the author a moderator?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "A list of reddit usernames (case-insensitive) to match against",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"LockActionJSONConfig": {
|
||||
"description": "Lock the Activity",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
@@ -318,23 +389,80 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"name",
|
||||
"kind"
|
||||
],
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PollingOptions": {
|
||||
"description": "You may specify polling options independently for submissions/comments",
|
||||
"properties": {
|
||||
"comments": {
|
||||
"description": "Polling options for comment events",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"default": 10000,
|
||||
"description": "Amount of time to wait between requests for new comments\n\nDefaults to 10 seconds",
|
||||
"format": "milliseconds",
|
||||
"type": "number"
|
||||
},
|
||||
"limit": {
|
||||
"default": 10,
|
||||
"description": "The number of new comments to pull on every request",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"limit",
|
||||
"interval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"submissions": {
|
||||
"description": "Polling options for submission events",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"default": 10000,
|
||||
"description": "Amount of time to wait between requests to /r/subreddit/new\n\nDefaults to 10 seconds",
|
||||
"format": "milliseconds",
|
||||
"type": "number"
|
||||
},
|
||||
"limit": {
|
||||
"default": 10,
|
||||
"description": "The number of submissions to pull from /r/subreddit/new on every request",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"limit",
|
||||
"interval"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"submissions",
|
||||
"comments"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RecentActivityRuleJSONConfig": {
|
||||
"description": "Checks a user's history for any Activity (Submission/Comment) in the subreddits specified in thresholds",
|
||||
"properties": {
|
||||
"authors": {
|
||||
"$ref": "#/definitions/AuthorOptions",
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note this is NOT the same as AuthorRule."
|
||||
"additionalProperties": false,
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.",
|
||||
"minProperties": 1
|
||||
},
|
||||
"kind": {
|
||||
"description": "The kind of rule to run",
|
||||
"enum": [
|
||||
"author",
|
||||
"recentActivity",
|
||||
"repeatSubmission"
|
||||
"recentActivity"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -355,14 +483,19 @@
|
||||
"items": {
|
||||
"$ref": "#/definitions/SubThreshold"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"usePostAsReference": {
|
||||
"useSubmissionAsReference": {
|
||||
"default": true,
|
||||
"description": "If activity is a Submission and is a link (not self-post) then only look at Submissions that contain this link, otherwise consider all activities.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"window": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DurationObject"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/ActivityWindowCriteria"
|
||||
},
|
||||
@@ -374,17 +507,44 @@
|
||||
}
|
||||
],
|
||||
"default": 15,
|
||||
"description": "Criteria for defining what set of activities should be considered. See ActivityWindowCriteria for descriptions of what different data types will do\n//@examples require('./interfaces.ts').windowExample"
|
||||
"description": "Criteria for defining what set of activities should be considered.\n\nThe value of this property may be either count OR duration -- to use both write it as an ActivityWindowCriteria\n\nSee ActivityWindowCriteria for descriptions of what count/duration do",
|
||||
"examples": [
|
||||
15,
|
||||
"PT1M",
|
||||
{
|
||||
"count": 10
|
||||
},
|
||||
{
|
||||
"duration": {
|
||||
"hours": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"count": 5,
|
||||
"duration": {
|
||||
"minutes": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"kind",
|
||||
"lookAt",
|
||||
"thresholds",
|
||||
"window",
|
||||
"useSubmissionAsReference",
|
||||
"name",
|
||||
"authors"
|
||||
],
|
||||
"required": [
|
||||
"kind",
|
||||
"thresholds",
|
||||
"window"
|
||||
"thresholds"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RemoveActionJSONConfig": {
|
||||
"description": "Remove the Activity",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"description": "The type of action that will be performed",
|
||||
@@ -402,37 +562,55 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"name",
|
||||
"kind"
|
||||
],
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RepeatSubmissionJSONConfig": {
|
||||
"description": "Checks a user's history for Submissions with identical content",
|
||||
"properties": {
|
||||
"authors": {
|
||||
"$ref": "#/definitions/AuthorOptions",
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped. Note this is NOT the same as AuthorRule."
|
||||
"additionalProperties": false,
|
||||
"description": "If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.",
|
||||
"minProperties": 1
|
||||
},
|
||||
"exclude": {
|
||||
"description": "Do not include Submissions from this list of Subreddits.\n\nA list of subreddits (case-insensitive) to look for. Do not include \"r/\" prefix.\n\nEX to match against /r/mealtimevideos and /r/askscience use [\"mealtimevideos\",\"askscience\"]",
|
||||
"examples": [
|
||||
"mealtimevideos",
|
||||
"askscience"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"gapAllowance": {
|
||||
"description": "The number of allowed non-identical Submissions between identical Submissions that can be ignored when checking against the threshold value",
|
||||
"type": "number"
|
||||
},
|
||||
"include": {
|
||||
"description": "Only include Submissions from this list of Subreddits.\n\nA list of subreddits (case-insensitive) to look for. Do not include \"r/\" prefix.\n\nEX to match against /r/mealtimevideos and /r/askscience use [\"mealtimevideos\",\"askscience\"]",
|
||||
"examples": [
|
||||
"mealtimevideos",
|
||||
"askscience"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"kind": {
|
||||
"description": "The kind of rule to run",
|
||||
"enum": [
|
||||
"author",
|
||||
"recentActivity",
|
||||
"repeatSubmission"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -442,28 +620,73 @@
|
||||
"type": "string"
|
||||
},
|
||||
"threshold": {
|
||||
"default": 5,
|
||||
"description": "The number of repeat submissions that will trigger the rule",
|
||||
"type": "number"
|
||||
},
|
||||
"usePostAsReference": {
|
||||
"useSubmissionAsReference": {
|
||||
"default": true,
|
||||
"description": "If activity is a Submission and is a link (not self-post) then only look at Submissions that contain this link, otherwise consider all activities.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"window": {
|
||||
"type": [
|
||||
"string",
|
||||
"number"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DurationObject"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/ActivityWindowCriteria"
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"string",
|
||||
"number"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": 15,
|
||||
"description": "Criteria for defining what set of activities should be considered.\n\nThe value of this property may be either count OR duration -- to use both write it as an ActivityWindowCriteria\n\nSee ActivityWindowCriteria for descriptions of what count/duration do",
|
||||
"examples": [
|
||||
15,
|
||||
"PT1M",
|
||||
{
|
||||
"count": 10
|
||||
},
|
||||
{
|
||||
"duration": {
|
||||
"hours": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"count": 5,
|
||||
"duration": {
|
||||
"minutes": 15
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"propertyOrder": [
|
||||
"kind",
|
||||
"threshold"
|
||||
"threshold",
|
||||
"gapAllowance",
|
||||
"include",
|
||||
"exclude",
|
||||
"window",
|
||||
"useSubmissionAsReference",
|
||||
"name",
|
||||
"authors"
|
||||
],
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ReportActionJSONConfig": {
|
||||
"description": "Report the Activity",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "The text of the report",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
@@ -482,6 +705,11 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"content",
|
||||
"kind",
|
||||
"name"
|
||||
],
|
||||
"required": [
|
||||
"content",
|
||||
"kind"
|
||||
@@ -492,7 +720,8 @@
|
||||
"description": "A RuleSet is a \"nested\" set of Rules that can be used to create more complex AND/OR behavior. Think of the outcome of a RuleSet as the result of all of it's Rules (based on condition)",
|
||||
"properties": {
|
||||
"condition": {
|
||||
"description": "Under what condition should a RuleSet's rules be \"successful\"? If 'OR' then ANY triggered rule result in a true outcome. If 'AND' then ALL rules must be triggered for the result to be true.",
|
||||
"default": "AND",
|
||||
"description": "Under what condition should a set of rules be considered \"successful\"?\n\nIf \"OR\" then ANY triggered rule results in success.\n\nIf \"AND\" then ALL rules must be triggered to result in success.",
|
||||
"enum": [
|
||||
"AND",
|
||||
"OR"
|
||||
@@ -513,11 +742,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"rules",
|
||||
"condition"
|
||||
],
|
||||
"required": [
|
||||
"condition",
|
||||
"rules"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -525,11 +758,17 @@
|
||||
"SubThreshold": {
|
||||
"properties": {
|
||||
"count": {
|
||||
"default": 1,
|
||||
"description": "The number of activities in each subreddit from the list that will trigger this rule",
|
||||
"minimum": 1,
|
||||
"type": "number"
|
||||
},
|
||||
"subreddits": {
|
||||
"description": "A list of subreddits (case-insensitive) to look for",
|
||||
"description": "A list of subreddits (case-insensitive) to look for. Do not include \"r/\" prefix.\n\nEX to match against /r/mealtimevideos and /r/askscience use [\"mealtimevideos\",\"askscience\"]",
|
||||
"examples": [
|
||||
"mealtimevideos",
|
||||
"askscience"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -537,6 +776,10 @@
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"count",
|
||||
"subreddits"
|
||||
],
|
||||
"required": [
|
||||
"subreddits"
|
||||
],
|
||||
@@ -545,14 +788,22 @@
|
||||
},
|
||||
"properties": {
|
||||
"checks": {
|
||||
"description": "A list of all the checks that should be run for a subreddit. Checks are split into two lists -- submission or comment -- based on kind and run independently. Checks in each list are run in the order found in the configuration. When a check \"passes\" and actions are performed any subsequent checks are skipped.",
|
||||
"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": {
|
||||
"$ref": "#/definitions/CheckJSONConfig"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"polling": {
|
||||
"$ref": "#/definitions/PollingOptions",
|
||||
"description": "You may specify polling options independently for submissions/comments"
|
||||
}
|
||||
},
|
||||
"propertyOrder": [
|
||||
"checks",
|
||||
"polling"
|
||||
],
|
||||
"required": [
|
||||
"checks"
|
||||
],
|
||||
|
||||
@@ -7,23 +7,17 @@ import {CommentStream, SubmissionStream} from "snoostorm";
|
||||
import pEvent from "p-event";
|
||||
import {RuleResult} from "../Rule";
|
||||
import {ConfigBuilder} from "../ConfigBuilder";
|
||||
import {PollingOptions} from "../Common/interfaces";
|
||||
|
||||
export interface ManagerOptions {
|
||||
submissions?: {
|
||||
limit?: number,
|
||||
interval?: number,
|
||||
},
|
||||
comments?: {
|
||||
limit?: number,
|
||||
interval?: number,
|
||||
}
|
||||
polling?: PollingOptions
|
||||
}
|
||||
|
||||
export class Manager {
|
||||
subreddit: Subreddit;
|
||||
client: Snoowrap;
|
||||
logger: Logger;
|
||||
pollOptions: ManagerOptions;
|
||||
pollOptions: PollingOptions;
|
||||
submissionChecks: SubmissionCheck[];
|
||||
commentChecks: CommentCheck[];
|
||||
|
||||
@@ -37,7 +31,7 @@ export class Manager {
|
||||
|
||||
const configBuilder = new ConfigBuilder({logger: this.logger});
|
||||
const [subChecks, commentChecks] = configBuilder.buildFromJson(sourceData);
|
||||
this.pollOptions = opts;
|
||||
this.pollOptions = opts.polling || {};
|
||||
this.subreddit = sub;
|
||||
this.client = client;
|
||||
this.submissionChecks = subChecks;
|
||||
|
||||
@@ -3,7 +3,7 @@ import Submission from "snoowrap/dist/objects/Submission";
|
||||
import {Duration, DurationUnitsObjectType} from "dayjs/plugin/duration";
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import Mustache from "mustache";
|
||||
import {AuthorOptions, IAuthor} from "../Rule";
|
||||
import {AuthorOptions, AuthorCriteria} from "../Rule";
|
||||
import {ActivityWindowCriteria, ActivityWindowType} from "../Common/interfaces";
|
||||
|
||||
export interface AuthorTypedActivitiesOptions extends AuthorActivitiesOptions {
|
||||
@@ -97,7 +97,7 @@ export const renderContent = async (content: string, data: (Submission | Comment
|
||||
return Mustache.render(content, {...templateData, ...additionalData});
|
||||
}
|
||||
|
||||
export const testAuthorCriteria = async (item: (Comment|Submission), authorOpts: IAuthor, include = true) => {
|
||||
export const testAuthorCriteria = async (item: (Comment|Submission), authorOpts: AuthorCriteria, include = true) => {
|
||||
// @ts-ignore
|
||||
const author: RedditUser = await item.author;
|
||||
for(const k of Object.keys(authorOpts)) {
|
||||
|
||||
Reference in New Issue
Block a user