feat: Implement rule that tests activity content against moderatehatespeech.com ML model using api #110

* Add mhs rule type and MHS credentials interface
* Implement MHS rule with similar criteria options to sentiment
* Allow testing against author history content
This commit is contained in:
FoxxMD
2022-09-19 16:20:18 -04:00
parent 8b0a582464
commit 02414478bf
13 changed files with 1359 additions and 5 deletions

View File

@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm"
import {RuleType} from "../../../Entities/RuleType";
export class mhs1663609045418 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager.getRepository(RuleType).save([
new RuleType('mhs'),
]);
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View File

@@ -1585,6 +1585,9 @@ export interface ThirdPartyCredentialsJsonConfig {
youtube?: {
apiKey: string
}
mhs?: {
apiKey: string
}
[key: string]: any
}

View File

@@ -19,10 +19,11 @@ import {DispatchActionJson} from "../Action/DispatchAction";
import {CancelDispatchActionJson} from "../Action/CancelDispatchAction";
import {ContributorActionJson} from "../Action/ContributorAction";
import {SentimentRuleJSONConfig} from "../Rule/SentimentRule";
import {MHSRuleJSONConfig} from "../Rule/MHSRule";
import {ModNoteActionJson} from "../Action/ModNoteAction";
import {IncludesData} from "./Infrastructure/Includes";
import { SubmissionActionJson } from "../Action/SubmissionAction";
export type RuleObjectJsonTypes = RecentActivityRuleJSONConfig | RepeatActivityJSONConfig | AuthorRuleJSONConfig | AttributionJSONConfig | HistoryJSONConfig | RegexRuleJSONConfig | RepostRuleJSONConfig | SentimentRuleJSONConfig
export type RuleObjectJsonTypes = RecentActivityRuleJSONConfig | RepeatActivityJSONConfig | AuthorRuleJSONConfig | AttributionJSONConfig | HistoryJSONConfig | RegexRuleJSONConfig | RepostRuleJSONConfig | SentimentRuleJSONConfig | MHSRuleJSONConfig
export type ActionJson = CommentActionJson | SubmissionActionJson | FlairActionJson | ReportActionJson | LockActionJson | RemoveActionJson | ApproveActionJson | BanActionJson | UserNoteActionJson | MessageActionJson | UserFlairActionJson | DispatchActionJson | CancelDispatchActionJson | ContributorActionJson | ModNoteActionJson | string | IncludesData;

420
src/Rule/MHSRule.ts Normal file
View File

@@ -0,0 +1,420 @@
import {Rule, RuleJSONConfig, RuleOptions} from "./index";
import {Comment} from "snoowrap";
import Submission from "snoowrap/dist/objects/Submission";
import {
asComment, boolToString,
formatNumber,
triggeredIndicator, windowConfigToWindowCriteria
} from "../util";
import got, {HTTPError} from 'got';
import dayjs from 'dayjs';
import {map as mapAsync} from 'async';
import {
comparisonTextOp,
GenericComparison,
parseGenericValueOrPercentComparison,
} from "../Common/Infrastructure/Comparisons";
import {ActivityWindowConfig, ActivityWindowCriteria} from "../Common/Infrastructure/ActivityWindow";
import {RuleResult} from "../Common/interfaces";
import {SnoowrapActivity} from "../Common/Infrastructure/Reddit";
import {CMError} from "../Utils/Errors";
import objectHash from "object-hash";
const formatConfidence = (val: number) => formatNumber(val * 100, {
suffix: '%',
toFixed: 2
});
export class MHSRule extends Rule {
criteria: MHSCriteria
historical?: HistoricalMHS;
ogConfig: MHSConfig
constructor(options: MHSRuleOptions) {
super(options);
if (this.resources.thirdPartyCredentials.mhs?.apiKey === undefined) {
throw new CMError(`MHS (moderatehatespeech.com) API Key has not been specified. It must be present in the bot config or top-level subreddit 'credentials' property.`);
}
const {
criteria,
historical,
} = options;
this.ogConfig = {
criteria,
historical
};
const {
flagged = true,
confidence,
testOn = ['body']
} = criteria || {};
this.criteria = {
flagged,
confidence: confidence !== undefined ? parseGenericValueOrPercentComparison(confidence) : undefined,
testOn,
}
if (options.historical !== undefined) {
const {
window,
criteria: historyCriteria,
mustMatchCurrent = false,
totalMatching = '> 0',
} = options.historical
let usedCriteria: MHSCriteria;
if (historyCriteria === undefined) {
usedCriteria = this.criteria;
} else {
const {
flagged: historyFlagged = true,
confidence: historyConfidence,
testOn: historyTestOn = ['body']
} = historyCriteria || {};
usedCriteria = {
flagged: historyFlagged,
confidence: historyConfidence !== undefined ? parseGenericValueOrPercentComparison(historyConfidence) : undefined,
testOn: historyTestOn,
}
}
this.historical = {
criteria: usedCriteria,
window: windowConfigToWindowCriteria(window),
mustMatchCurrent,
totalMatching: parseGenericValueOrPercentComparison(totalMatching),
};
}
}
getKind(): string {
return 'mhs';
}
getSpecificPremise(): object {
return this.ogConfig;
}
protected async process(item: Submission | Comment): Promise<[boolean, RuleResult]> {
let ogResult = await this.testActivity(item, this.criteria);
let historicResults: MHSCriteriaResult[] | undefined;
let historicalCriteriaTest: string | undefined;
if (this.historical !== undefined && (!this.historical.mustMatchCurrent || ogResult.passed)) {
const {
criteria,
window,
} = this.historical;
const history = await this.resources.getAuthorActivities(item.author, window);
historicResults = await mapAsync(history, async (x: SnoowrapActivity) => await this.testActivity(x, criteria)); // history.map(x => this.testActivity(x, sentiment));
}
const logSummary: string[] = [];
let triggered = false;
let humanWindow: string | undefined;
let historicalPassed: string | undefined;
let totalMatchingText: string | undefined;
if (historicResults === undefined) {
triggered = ogResult.passed;
logSummary.push(`Current Activity MHS Test: ${ogResult.summary}`);
if (!triggered && this.historical !== undefined && this.historical.mustMatchCurrent) {
logSummary.push(`Did not check Historical because 'mustMatchCurrent' is true`);
}
} else {
const {
totalMatching,
criteria,
} = this.historical as HistoricalMHS;
historicalCriteriaTest = mhsCriteriaTestDisplay(criteria);
totalMatchingText = totalMatching.displayText;
const allResults = historicResults
const passed = allResults.filter(x => x.passed);
const firstActivity = allResults[0].activity;
const lastActivity = allResults[allResults.length - 1].activity;
const humanRange = dayjs.duration(dayjs(firstActivity.created_utc * 1000).diff(dayjs(lastActivity.created_utc * 1000))).humanize();
humanWindow = `${allResults.length} Activities (${humanRange})`;
const {operator, value, isPercent} = totalMatching;
if (isPercent) {
const passPercentVal = passed.length / allResults.length
triggered = comparisonTextOp(passPercentVal, operator, (value / 100));
historicalPassed = `${passed.length} (${formatNumber(passPercentVal)}%)`;
} else {
triggered = comparisonTextOp(passed.length, operator, value);
historicalPassed = `${passed.length}`;
}
logSummary.push(`${triggeredIndicator(triggered)} ${historicalPassed} historical activities of ${humanWindow} passed MHS criteria '${historicalCriteriaTest}' which ${triggered ? 'MET' : 'DID NOT MEET'} threshold '${totalMatching.displayText}'`);
}
const result = logSummary.join(' || ');
this.logger.verbose(result);
return Promise.resolve([triggered, this.getResult(triggered, {
result,
data: {
results: {
triggered,
criteriaTest: mhsCriteriaTestDisplay(this.criteria),
historicalCriteriaTest,
window: humanWindow,
totalMatching: totalMatchingText
}
}
})]);
}
protected async testActivity(a: SnoowrapActivity, criteria: MHSCriteria): Promise<MHSCriteriaResult> {
const content = [];
if (asComment(a)) {
content.push(a.body);
} else {
if (criteria.testOn.includes('title')) {
content.push(a.title);
}
if (criteria.testOn.includes('body') && a.is_self) {
content.push(a.selftext);
}
}
const mhsResult = await this.getMHSResponse(content.join(' '));
const {
flagged,
confidence
} = criteria;
let flaggedPassed: boolean | undefined;
let confPassed: boolean | undefined;
let summary = [];
if (confidence !== undefined) {
const {operator, value} = confidence;
confPassed = comparisonTextOp(mhsResult.confidence * 100, operator, value);
summary.push(`Confidence test (${confidence.displayText}) ${confPassed ? 'PASSED' : 'DID NOT PASS'} MHS confidence of ${formatConfidence(mhsResult.confidence)}`)
}
if (flagged !== undefined) {
flaggedPassed = flagged ? mhsResult.class === 'flag' : mhsResult.class === 'normal';
summary.push(`Flagged pass condition of ${flagged} (${flagged ? 'toxic' : 'normal'}) ${flaggedPassed ? 'MATCHED' : 'DID NOT MATCH'} MHS flag '${mhsResult.class === 'flag' ? 'toxic' : 'normal'}' ${confidence === undefined ? ` (${formatConfidence(mhsResult.confidence)} confidence)` : ''}`);
}
const passed = (flaggedPassed === undefined || flaggedPassed) && (confPassed === undefined || confPassed);
return {
activity: a,
criteria,
mhsResult,
passed,
summary: `${triggeredIndicator(passed)} ${summary.join(' | ')}`
}
}
protected async getMHSResponse(content: string): Promise<MHSResponse> {
const hash = objectHash.sha1({content});
const key = `mhs-${hash}`;
if (this.resources.wikiTTL !== false) {
let res = await this.resources.cache.get(key) as undefined | null | MHSResponse;
if(res !== undefined && res !== null) {
// don't cache bad responses
if(res.response.toLowerCase() === 'success')
{
return res;
}
}
res = await this.callMHS(content);
if(res.response.toLowerCase() === 'success') {
await this.resources.cache.set(key, res, {ttl: this.resources.wikiTTL});
}
return res;
}
return this.callMHS(content);
}
protected async callMHS(content: string): Promise<MHSResponse> {
try {
return await got.post(`https://api.moderatehatespeech.com/api/v1/moderate/`, {
headers: {
'Content-Type': `application/json`,
},
json: {
token: this.resources.thirdPartyCredentials.mhs?.apiKey,
text: content
},
}).json() as MHSResponse;
} catch (err: any) {
let error: string | undefined = undefined;
if (err instanceof HTTPError) {
error = err.response.statusMessage;
if (typeof err.response.body === 'string') {
error = `(${err.response.statusCode}) ${err.response.body}`;
}
}
throw new CMError(`MHS request failed${error !== undefined ? ` with error: ${error}` : ''}`, {cause: err});
}
}
}
const mhsCriteriaTestDisplay = (criteria: MHSCriteria) => {
const summary = [];
if (criteria.flagged !== undefined) {
summary.push(`${criteria.flagged ? 'IS FLAGGED' : 'IS NOT FLAGGED'} as toxic`);
}
if (criteria.confidence !== undefined) {
summary.push(`MHS confidence is ${criteria.confidence.displayText}`);
}
return summary.join(' AND ');
}
interface MHSResponse {
confidence: number
response: string
class: 'flag' | 'normal'
}
interface MHSCriteriaResult {
mhsResult: MHSResponse
criteria: MHSCriteria
passed: boolean
summary: string,
activity: SnoowrapActivity
}
/**
* Test the content of Activities from the Author history against MHS criteria
*
* If this is defined then the `totalMatching` threshold must pass for the Rule to trigger
*
* If `criteria` is defined here it overrides the top-level `criteria` value
*
* */
interface HistoricalMHSConfig {
window: ActivityWindowConfig
criteria?: MHSCriteriaConfig
/**
* When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history
*
* @default false
* */
mustMatchCurrent?: boolean
/**
* A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test
*
* The syntax is `(< OR > OR <= OR >=) <number>[percent sign]`
*
* * EX `> 12` => greater than 12 activities passed given `criteria` test
* * EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test
*
* @pattern ^\s*(>|>=|<|<=)\s*(\d+)\s*(%?)(.*)$
* @default "> 0"
* @examples ["> 0","> 10%"]
* */
totalMatching: string
}
interface HistoricalMHS extends Omit<HistoricalMHSConfig, | 'window' | 'totalMatching' | 'criteria'> {
window: ActivityWindowCriteria
criteria: MHSCriteria
totalMatching: GenericComparison
}
/**
* Criteria used to trigger based on MHS results
*
* If both `flagged` and `confidence` are specified then both conditions must pass.
*
* By default, only `flagged` is defined as `true`
* */
interface MHSCriteriaConfig {
/**
* Test if MHS considers content flagged as toxic or not
*
* @default true
* */
flagged?: boolean
/**
* A string containing a comparison operator and a value to compare against the confidence returned from MHS
*
* The syntax is `(< OR > OR <= OR >=) <number>`
*
* * EX `> 50` => MHS confidence is greater than 50%
*
* @pattern ^\s*(>|>=|<|<=)\s*(\d+)\s*(%?)(.*)$
* @examples ["> 50"]
* */
confidence?: string
/**
* Which content from an Activity to send to MHS
*
* Only used if the Activity being tested is a Submission -- Comments can be only tested against their body
*
* If more than one type of content is specified then all text is tested together as one string
*
* @default ["body"]
* */
testOn?: ('title' | 'body')[]
}
interface MHSCriteria extends Omit<MHSCriteriaConfig, 'confidence'> {
confidence?: GenericComparison
testOn: ('title' | 'body')[]
}
interface MHSConfig {
criteria?: MHSCriteriaConfig
/**
* run MHS on Activities from the Author history
*
* If this is defined then the `totalMatching` threshold must pass for the Rule to trigger
*
* If `criteria` is defined here it overrides the top-level `criteria` value
*
* */
historical?: HistoricalMHSConfig
}
export interface MHSRuleOptions extends MHSConfig, RuleOptions {
}
/**
* Test content of an Activity against the MHS toxicity model for reddit content
*
* Running this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.
*
* More info:
*
* * https://moderatehatespeech.com/docs/
* * https://moderatehatespeech.com/
*
* */
export interface MHSRuleJSONConfig extends MHSConfig, RuleJSONConfig {
/**
* @examples ["mhs"]
* @default mhs
* */
kind: 'mhs'
}
export default MHSRule;

View File

@@ -12,6 +12,7 @@ import {RepostRule, RepostRuleJSONConfig} from "./RepostRule";
import {StructuredFilter} from "../Common/Infrastructure/Filters/FilterShapes";
import {SentimentRule, SentimentRuleJSONConfig} from "./SentimentRule";
import {StructuredRuleConfigObject} from "../Common/Infrastructure/RuleShapes";
import {MHSRuleJSONConfig, MHSRule} from "./MHSRule";
export function ruleFactory
(config: StructuredRuleConfigObject, logger: Logger, subredditName: string, resources: SubredditResources, client: Snoowrap): Rule {
@@ -42,6 +43,9 @@ export function ruleFactory
case 'sentiment':
cfg = config as StructuredFilter<SentimentRuleJSONConfig>;
return new SentimentRule({...cfg, logger, subredditName, resources, client});
case 'mhs':
cfg = config as StructuredFilter<MHSRuleJSONConfig>;
return new MHSRule({...cfg, logger, subredditName, resources, client});
default:
throw new Error(`Rule with kind '${config.kind}' was not recognized.`);
}

View File

@@ -185,5 +185,5 @@ export interface RuleJSONConfig extends IRule {
* The kind of rule to run
* @examples ["recentActivity", "repeatActivity", "author", "attribution", "history"]
*/
kind: 'recentActivity' | 'repeatActivity' | 'author' | 'attribution' | 'history' | 'regex' | 'repost' | 'sentiment'
kind: 'recentActivity' | 'repeatActivity' | 'author' | 'attribution' | 'history' | 'regex' | 'repost' | 'sentiment' | 'mhs'
}

View File

@@ -1650,6 +1650,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},
@@ -2782,6 +2785,55 @@
},
"type": "object"
},
"HistoricalMHSConfig": {
"description": "Test the content of Activities from the Author history against MHS criteria\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value",
"properties": {
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"mustMatchCurrent": {
"default": false,
"description": "When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history",
"type": "boolean"
},
"totalMatching": {
"default": "> 0",
"description": "A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities passed given `criteria` test\n* EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test",
"examples": [
"> 0",
"> 10%"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"window": {
"anyOf": [
{
"$ref": "#/definitions/DurationObject"
},
{
"$ref": "#/definitions/FullActivityWindowConfig"
},
{
"type": [
"string",
"number"
]
}
],
"description": "A value to define the range of Activities to retrieve.\n\nAcceptable values:\n\n**`ActivityWindowCriteria` object**\n\nAllows specify multiple range properties and more specific behavior\n\n**A `number` of Activities to retrieve**\n\n* EX `100` => 100 Activities\n\n*****\n\nAny of the below values that specify the amount of time to subtract from `NOW` to create a time range IE `NOW <---> [duration] ago`\n\nAcceptable values:\n\n**A `string` consisting of a value and a [Day.js](https://day.js.org/docs/en/durations/creating#list-of-all-available-units) time UNIT**\n\n* EX `9 days` => Range is `NOW <---> 9 days ago`\n\n**A [Day.js](https://day.js.org/docs/en/durations/creating) `object`**\n\n* EX `{\"days\": 90, \"minutes\": 15}` => Range is `NOW <---> 90 days and 15 minutes ago`\n\n**An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) `string`**\n\n* EX `PT15M` => 15 minutes => Range is `NOW <----> 15 minutes ago`",
"examples": [
"90 days"
]
}
},
"required": [
"totalMatching",
"window"
],
"type": "object"
},
"HistoricalSentimentConfig": {
"description": "Test the Sentiment of Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `sentiment` is defined here it overrides the top-level `sentiment` value",
"properties": {
@@ -3251,6 +3303,126 @@
],
"type": "object"
},
"MHSCriteriaConfig": {
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`",
"properties": {
"confidence": {
"description": "A string containing a comparison operator and a value to compare against the confidence returned from MHS\n\nThe syntax is `(< OR > OR <= OR >=) <number>`\n\n* EX `> 50` => MHS confidence is greater than 50%",
"examples": [
"> 50"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"flagged": {
"default": true,
"description": "Test if MHS considers content flagged as toxic or not",
"type": "boolean"
},
"testOn": {
"default": [
"body"
],
"description": "Which content from an Activity to send to MHS\n\nOnly used if the Activity being tested is a Submission -- Comments can be only tested against their body\n\nIf more than one type of content is specified then all text is tested together as one string",
"items": {
"enum": [
"body",
"title"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"MHSRuleJSONConfig": {
"description": "Test content of an Activity against the MHS toxicity model for reddit content\n\nRunning this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.\n\nMore info:\n\n* https://moderatehatespeech.com/docs/\n* https://moderatehatespeech.com/",
"properties": {
"authorIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/AuthorCriteria"
},
{
"$ref": "#/definitions/NamedCriteria<AuthorCriteria>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<AuthorCriteria>"
}
],
"description": "If present then these Author criteria are checked before running the Check. If criteria fails then the Check will fail."
},
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"historical": {
"$ref": "#/definitions/HistoricalMHSConfig",
"description": "run MHS on Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value"
},
"itemIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
{
"$ref": "#/definitions/CommentState"
},
{
"$ref": "#/definitions/NamedCriteria<TypedActivityState>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<TypedActivityState>"
}
],
"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}]]"
},
"kind": {
"default": "mhs",
"description": "The kind of rule to run",
"enum": [
"mhs"
],
"examples": [
"mhs"
],
"type": "string"
},
"name": {
"description": "An optional, but highly recommended, friendly name for this rule. If not present will default to `kind`.\n\nCan only contain letters, numbers, underscore, spaces, and dashes\n\nname is used to reference Rule result data during Action content templating. See CommentAction or ReportAction for more details.",
"examples": [
"myNewRule"
],
"pattern": "^[a-zA-Z]([\\w -]*[\\w])?$",
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
"MessageActionJson": {
"description": "Send a private message to the Author of the Activity.",
"properties": {
@@ -5196,6 +5368,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"type": "string"
}
@@ -5989,6 +6164,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},
@@ -6265,6 +6443,17 @@
"ThirdPartyCredentialsJsonConfig": {
"additionalProperties": {},
"properties": {
"mhs": {
"properties": {
"apiKey": {
"type": "string"
}
},
"required": [
"apiKey"
],
"type": "object"
},
"youtube": {
"properties": {
"apiKey": {

View File

@@ -1473,6 +1473,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},
@@ -2496,6 +2499,55 @@
},
"type": "object"
},
"HistoricalMHSConfig": {
"description": "Test the content of Activities from the Author history against MHS criteria\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value",
"properties": {
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"mustMatchCurrent": {
"default": false,
"description": "When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history",
"type": "boolean"
},
"totalMatching": {
"default": "> 0",
"description": "A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities passed given `criteria` test\n* EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test",
"examples": [
"> 0",
"> 10%"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"window": {
"anyOf": [
{
"$ref": "#/definitions/DurationObject"
},
{
"$ref": "#/definitions/FullActivityWindowConfig"
},
{
"type": [
"string",
"number"
]
}
],
"description": "A value to define the range of Activities to retrieve.\n\nAcceptable values:\n\n**`ActivityWindowCriteria` object**\n\nAllows specify multiple range properties and more specific behavior\n\n**A `number` of Activities to retrieve**\n\n* EX `100` => 100 Activities\n\n*****\n\nAny of the below values that specify the amount of time to subtract from `NOW` to create a time range IE `NOW <---> [duration] ago`\n\nAcceptable values:\n\n**A `string` consisting of a value and a [Day.js](https://day.js.org/docs/en/durations/creating#list-of-all-available-units) time UNIT**\n\n* EX `9 days` => Range is `NOW <---> 9 days ago`\n\n**A [Day.js](https://day.js.org/docs/en/durations/creating) `object`**\n\n* EX `{\"days\": 90, \"minutes\": 15}` => Range is `NOW <---> 90 days and 15 minutes ago`\n\n**An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) `string`**\n\n* EX `PT15M` => 15 minutes => Range is `NOW <----> 15 minutes ago`",
"examples": [
"90 days"
]
}
},
"required": [
"totalMatching",
"window"
],
"type": "object"
},
"HistoricalSentimentConfig": {
"description": "Test the Sentiment of Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `sentiment` is defined here it overrides the top-level `sentiment` value",
"properties": {
@@ -2965,6 +3017,126 @@
],
"type": "object"
},
"MHSCriteriaConfig": {
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`",
"properties": {
"confidence": {
"description": "A string containing a comparison operator and a value to compare against the confidence returned from MHS\n\nThe syntax is `(< OR > OR <= OR >=) <number>`\n\n* EX `> 50` => MHS confidence is greater than 50%",
"examples": [
"> 50"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"flagged": {
"default": true,
"description": "Test if MHS considers content flagged as toxic or not",
"type": "boolean"
},
"testOn": {
"default": [
"body"
],
"description": "Which content from an Activity to send to MHS\n\nOnly used if the Activity being tested is a Submission -- Comments can be only tested against their body\n\nIf more than one type of content is specified then all text is tested together as one string",
"items": {
"enum": [
"body",
"title"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"MHSRuleJSONConfig": {
"description": "Test content of an Activity against the MHS toxicity model for reddit content\n\nRunning this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.\n\nMore info:\n\n* https://moderatehatespeech.com/docs/\n* https://moderatehatespeech.com/",
"properties": {
"authorIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/AuthorCriteria"
},
{
"$ref": "#/definitions/NamedCriteria<AuthorCriteria>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<AuthorCriteria>"
}
],
"description": "If present then these Author criteria are checked before running the Check. If criteria fails then the Check will fail."
},
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"historical": {
"$ref": "#/definitions/HistoricalMHSConfig",
"description": "run MHS on Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value"
},
"itemIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
{
"$ref": "#/definitions/CommentState"
},
{
"$ref": "#/definitions/NamedCriteria<TypedActivityState>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<TypedActivityState>"
}
],
"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}]]"
},
"kind": {
"default": "mhs",
"description": "The kind of rule to run",
"enum": [
"mhs"
],
"examples": [
"mhs"
],
"type": "string"
},
"name": {
"description": "An optional, but highly recommended, friendly name for this rule. If not present will default to `kind`.\n\nCan only contain letters, numbers, underscore, spaces, and dashes\n\nname is used to reference Rule result data during Action content templating. See CommentAction or ReportAction for more details.",
"examples": [
"myNewRule"
],
"pattern": "^[a-zA-Z]([\\w -]*[\\w])?$",
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
"MessageActionJson": {
"description": "Send a private message to the Author of the Activity.",
"properties": {
@@ -4770,6 +4942,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"type": "string"
}
@@ -5433,6 +5608,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},

View File

@@ -187,6 +187,17 @@
},
"BotCredentialsJsonConfig": {
"properties": {
"mhs": {
"properties": {
"apiKey": {
"type": "string"
}
},
"required": [
"apiKey"
],
"type": "object"
},
"reddit": {
"$ref": "#/definitions/RedditCredentials"
},
@@ -949,7 +960,7 @@
"file": {
"allOf": [
{
"$ref": "#/definitions/Omit<DailyRotateFileTransportOptions,\"stream\"|\"log\"|\"dirname\"|\"options\"|\"handleRejections\"|\"format\"|\"handleExceptions\"|\"logv\"|\"close\">"
"$ref": "#/definitions/Omit<DailyRotateFileTransportOptions,\"stream\"|\"log\"|\"options\"|\"dirname\"|\"handleRejections\"|\"format\"|\"handleExceptions\"|\"logv\"|\"close\">"
},
{
"properties": {
@@ -1396,7 +1407,7 @@
],
"type": "object"
},
"Omit<DailyRotateFileTransportOptions,\"stream\"|\"log\"|\"dirname\"|\"options\"|\"handleRejections\"|\"format\"|\"handleExceptions\"|\"logv\"|\"close\">": {
"Omit<DailyRotateFileTransportOptions,\"stream\"|\"log\"|\"options\"|\"dirname\"|\"handleRejections\"|\"format\"|\"handleExceptions\"|\"logv\"|\"close\">": {
"properties": {
"auditFile": {
"description": "A string representing the name of the name of the audit file. (default: './hash-audit.json')",
@@ -2077,6 +2088,17 @@
"ThirdPartyCredentialsJsonConfig": {
"additionalProperties": {},
"properties": {
"mhs": {
"properties": {
"apiKey": {
"type": "string"
}
},
"required": [
"apiKey"
],
"type": "object"
},
"youtube": {
"properties": {
"apiKey": {

View File

@@ -28,6 +28,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"type": "string"
}
@@ -1345,6 +1348,55 @@
},
"type": "object"
},
"HistoricalMHSConfig": {
"description": "Test the content of Activities from the Author history against MHS criteria\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value",
"properties": {
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"mustMatchCurrent": {
"default": false,
"description": "When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history",
"type": "boolean"
},
"totalMatching": {
"default": "> 0",
"description": "A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities passed given `criteria` test\n* EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test",
"examples": [
"> 0",
"> 10%"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"window": {
"anyOf": [
{
"$ref": "#/definitions/DurationObject"
},
{
"$ref": "#/definitions/FullActivityWindowConfig"
},
{
"type": [
"string",
"number"
]
}
],
"description": "A value to define the range of Activities to retrieve.\n\nAcceptable values:\n\n**`ActivityWindowCriteria` object**\n\nAllows specify multiple range properties and more specific behavior\n\n**A `number` of Activities to retrieve**\n\n* EX `100` => 100 Activities\n\n*****\n\nAny of the below values that specify the amount of time to subtract from `NOW` to create a time range IE `NOW <---> [duration] ago`\n\nAcceptable values:\n\n**A `string` consisting of a value and a [Day.js](https://day.js.org/docs/en/durations/creating#list-of-all-available-units) time UNIT**\n\n* EX `9 days` => Range is `NOW <---> 9 days ago`\n\n**A [Day.js](https://day.js.org/docs/en/durations/creating) `object`**\n\n* EX `{\"days\": 90, \"minutes\": 15}` => Range is `NOW <---> 90 days and 15 minutes ago`\n\n**An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) `string`**\n\n* EX `PT15M` => 15 minutes => Range is `NOW <----> 15 minutes ago`",
"examples": [
"90 days"
]
}
},
"required": [
"totalMatching",
"window"
],
"type": "object"
},
"HistoricalSentimentConfig": {
"description": "Test the Sentiment of Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `sentiment` is defined here it overrides the top-level `sentiment` value",
"properties": {
@@ -1722,6 +1774,126 @@
],
"type": "object"
},
"MHSCriteriaConfig": {
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`",
"properties": {
"confidence": {
"description": "A string containing a comparison operator and a value to compare against the confidence returned from MHS\n\nThe syntax is `(< OR > OR <= OR >=) <number>`\n\n* EX `> 50` => MHS confidence is greater than 50%",
"examples": [
"> 50"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"flagged": {
"default": true,
"description": "Test if MHS considers content flagged as toxic or not",
"type": "boolean"
},
"testOn": {
"default": [
"body"
],
"description": "Which content from an Activity to send to MHS\n\nOnly used if the Activity being tested is a Submission -- Comments can be only tested against their body\n\nIf more than one type of content is specified then all text is tested together as one string",
"items": {
"enum": [
"body",
"title"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"MHSRuleJSONConfig": {
"description": "Test content of an Activity against the MHS toxicity model for reddit content\n\nRunning this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.\n\nMore info:\n\n* https://moderatehatespeech.com/docs/\n* https://moderatehatespeech.com/",
"properties": {
"authorIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/AuthorCriteria"
},
{
"$ref": "#/definitions/NamedCriteria<AuthorCriteria>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<AuthorCriteria>"
}
],
"description": "If present then these Author criteria are checked before running the Check. If criteria fails then the Check will fail."
},
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"historical": {
"$ref": "#/definitions/HistoricalMHSConfig",
"description": "run MHS on Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value"
},
"itemIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
{
"$ref": "#/definitions/CommentState"
},
{
"$ref": "#/definitions/NamedCriteria<TypedActivityState>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<TypedActivityState>"
}
],
"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}]]"
},
"kind": {
"default": "mhs",
"description": "The kind of rule to run",
"enum": [
"mhs"
],
"examples": [
"mhs"
],
"type": "string"
},
"name": {
"description": "An optional, but highly recommended, friendly name for this rule. If not present will default to `kind`.\n\nCan only contain letters, numbers, underscore, spaces, and dashes\n\nname is used to reference Rule result data during Action content templating. See CommentAction or ReportAction for more details.",
"examples": [
"myNewRule"
],
"pattern": "^[a-zA-Z]([\\w -]*[\\w])?$",
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
"ModLogCriteria": {
"properties": {
"action": {

View File

@@ -1313,6 +1313,55 @@
},
"type": "object"
},
"HistoricalMHSConfig": {
"description": "Test the content of Activities from the Author history against MHS criteria\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value",
"properties": {
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"mustMatchCurrent": {
"default": false,
"description": "When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history",
"type": "boolean"
},
"totalMatching": {
"default": "> 0",
"description": "A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities passed given `criteria` test\n* EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test",
"examples": [
"> 0",
"> 10%"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"window": {
"anyOf": [
{
"$ref": "#/definitions/DurationObject"
},
{
"$ref": "#/definitions/FullActivityWindowConfig"
},
{
"type": [
"string",
"number"
]
}
],
"description": "A value to define the range of Activities to retrieve.\n\nAcceptable values:\n\n**`ActivityWindowCriteria` object**\n\nAllows specify multiple range properties and more specific behavior\n\n**A `number` of Activities to retrieve**\n\n* EX `100` => 100 Activities\n\n*****\n\nAny of the below values that specify the amount of time to subtract from `NOW` to create a time range IE `NOW <---> [duration] ago`\n\nAcceptable values:\n\n**A `string` consisting of a value and a [Day.js](https://day.js.org/docs/en/durations/creating#list-of-all-available-units) time UNIT**\n\n* EX `9 days` => Range is `NOW <---> 9 days ago`\n\n**A [Day.js](https://day.js.org/docs/en/durations/creating) `object`**\n\n* EX `{\"days\": 90, \"minutes\": 15}` => Range is `NOW <---> 90 days and 15 minutes ago`\n\n**An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) `string`**\n\n* EX `PT15M` => 15 minutes => Range is `NOW <----> 15 minutes ago`",
"examples": [
"90 days"
]
}
},
"required": [
"totalMatching",
"window"
],
"type": "object"
},
"HistoricalSentimentConfig": {
"description": "Test the Sentiment of Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `sentiment` is defined here it overrides the top-level `sentiment` value",
"properties": {
@@ -1690,6 +1739,126 @@
],
"type": "object"
},
"MHSCriteriaConfig": {
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`",
"properties": {
"confidence": {
"description": "A string containing a comparison operator and a value to compare against the confidence returned from MHS\n\nThe syntax is `(< OR > OR <= OR >=) <number>`\n\n* EX `> 50` => MHS confidence is greater than 50%",
"examples": [
"> 50"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"flagged": {
"default": true,
"description": "Test if MHS considers content flagged as toxic or not",
"type": "boolean"
},
"testOn": {
"default": [
"body"
],
"description": "Which content from an Activity to send to MHS\n\nOnly used if the Activity being tested is a Submission -- Comments can be only tested against their body\n\nIf more than one type of content is specified then all text is tested together as one string",
"items": {
"enum": [
"body",
"title"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"MHSRuleJSONConfig": {
"description": "Test content of an Activity against the MHS toxicity model for reddit content\n\nRunning this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.\n\nMore info:\n\n* https://moderatehatespeech.com/docs/\n* https://moderatehatespeech.com/",
"properties": {
"authorIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/AuthorCriteria"
},
{
"$ref": "#/definitions/NamedCriteria<AuthorCriteria>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<AuthorCriteria>"
}
],
"description": "If present then these Author criteria are checked before running the Check. If criteria fails then the Check will fail."
},
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"historical": {
"$ref": "#/definitions/HistoricalMHSConfig",
"description": "run MHS on Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value"
},
"itemIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
{
"$ref": "#/definitions/CommentState"
},
{
"$ref": "#/definitions/NamedCriteria<TypedActivityState>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<TypedActivityState>"
}
],
"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}]]"
},
"kind": {
"default": "mhs",
"description": "The kind of rule to run",
"enum": [
"mhs"
],
"examples": [
"mhs"
],
"type": "string"
},
"name": {
"description": "An optional, but highly recommended, friendly name for this rule. If not present will default to `kind`.\n\nCan only contain letters, numbers, underscore, spaces, and dashes\n\nname is used to reference Rule result data during Action content templating. See CommentAction or ReportAction for more details.",
"examples": [
"myNewRule"
],
"pattern": "^[a-zA-Z]([\\w -]*[\\w])?$",
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
"ModLogCriteria": {
"properties": {
"action": {
@@ -3552,6 +3721,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"type": "string"
}

View File

@@ -1470,6 +1470,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},
@@ -2563,6 +2566,55 @@
},
"type": "object"
},
"HistoricalMHSConfig": {
"description": "Test the content of Activities from the Author history against MHS criteria\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value",
"properties": {
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"mustMatchCurrent": {
"default": false,
"description": "When `true` the original Activity being checked MUST pass its criteria before the Rule considers any history",
"type": "boolean"
},
"totalMatching": {
"default": "> 0",
"description": "A string containing a comparison operator and a value to compare Activities from history that pass the given `criteria` test\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities passed given `criteria` test\n* EX `<= 10%` => less than 10% of all Activities from history passed given `criteria` test",
"examples": [
"> 0",
"> 10%"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"window": {
"anyOf": [
{
"$ref": "#/definitions/DurationObject"
},
{
"$ref": "#/definitions/FullActivityWindowConfig"
},
{
"type": [
"string",
"number"
]
}
],
"description": "A value to define the range of Activities to retrieve.\n\nAcceptable values:\n\n**`ActivityWindowCriteria` object**\n\nAllows specify multiple range properties and more specific behavior\n\n**A `number` of Activities to retrieve**\n\n* EX `100` => 100 Activities\n\n*****\n\nAny of the below values that specify the amount of time to subtract from `NOW` to create a time range IE `NOW <---> [duration] ago`\n\nAcceptable values:\n\n**A `string` consisting of a value and a [Day.js](https://day.js.org/docs/en/durations/creating#list-of-all-available-units) time UNIT**\n\n* EX `9 days` => Range is `NOW <---> 9 days ago`\n\n**A [Day.js](https://day.js.org/docs/en/durations/creating) `object`**\n\n* EX `{\"days\": 90, \"minutes\": 15}` => Range is `NOW <---> 90 days and 15 minutes ago`\n\n**An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) `string`**\n\n* EX `PT15M` => 15 minutes => Range is `NOW <----> 15 minutes ago`",
"examples": [
"90 days"
]
}
},
"required": [
"totalMatching",
"window"
],
"type": "object"
},
"HistoricalSentimentConfig": {
"description": "Test the Sentiment of Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `sentiment` is defined here it overrides the top-level `sentiment` value",
"properties": {
@@ -3032,6 +3084,126 @@
],
"type": "object"
},
"MHSCriteriaConfig": {
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`",
"properties": {
"confidence": {
"description": "A string containing a comparison operator and a value to compare against the confidence returned from MHS\n\nThe syntax is `(< OR > OR <= OR >=) <number>`\n\n* EX `> 50` => MHS confidence is greater than 50%",
"examples": [
"> 50"
],
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"flagged": {
"default": true,
"description": "Test if MHS considers content flagged as toxic or not",
"type": "boolean"
},
"testOn": {
"default": [
"body"
],
"description": "Which content from an Activity to send to MHS\n\nOnly used if the Activity being tested is a Submission -- Comments can be only tested against their body\n\nIf more than one type of content is specified then all text is tested together as one string",
"items": {
"enum": [
"body",
"title"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"MHSRuleJSONConfig": {
"description": "Test content of an Activity against the MHS toxicity model for reddit content\n\nRunning this Rule with no configuration will use a default configuration that will cause the Rule to trigger if MHS flags the content of the Activity as toxic.\n\nMore info:\n\n* https://moderatehatespeech.com/docs/\n* https://moderatehatespeech.com/",
"properties": {
"authorIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/AuthorCriteria"
},
{
"$ref": "#/definitions/NamedCriteria<AuthorCriteria>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<AuthorCriteria>"
}
],
"description": "If present then these Author criteria are checked before running the Check. If criteria fails then the Check will fail."
},
"criteria": {
"$ref": "#/definitions/MHSCriteriaConfig",
"description": "Criteria used to trigger based on MHS results\n\nIf both `flagged` and `confidence` are specified then both conditions must pass.\n\nBy default, only `flagged` is defined as `true`"
},
"historical": {
"$ref": "#/definitions/HistoricalMHSConfig",
"description": "run MHS on Activities from the Author history\n\nIf this is defined then the `totalMatching` threshold must pass for the Rule to trigger\n\nIf `criteria` is defined here it overrides the top-level `criteria` value"
},
"itemIs": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/SubmissionState"
},
{
"$ref": "#/definitions/CommentState"
},
{
"$ref": "#/definitions/NamedCriteria<TypedActivityState>"
},
{
"type": "string"
}
]
},
"type": "array"
},
{
"$ref": "#/definitions/FilterOptionsJson<TypedActivityState>"
}
],
"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}]]"
},
"kind": {
"default": "mhs",
"description": "The kind of rule to run",
"enum": [
"mhs"
],
"examples": [
"mhs"
],
"type": "string"
},
"name": {
"description": "An optional, but highly recommended, friendly name for this rule. If not present will default to `kind`.\n\nCan only contain letters, numbers, underscore, spaces, and dashes\n\nname is used to reference Rule result data during Action content templating. See CommentAction or ReportAction for more details.",
"examples": [
"myNewRule"
],
"pattern": "^[a-zA-Z]([\\w -]*[\\w])?$",
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
"MessageActionJson": {
"description": "Send a private message to the Author of the Activity.",
"properties": {
@@ -4837,6 +5009,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"type": "string"
}
@@ -5630,6 +5805,9 @@
{
"$ref": "#/definitions/SentimentRuleJSONConfig"
},
{
"$ref": "#/definitions/MHSRuleJSONConfig"
},
{
"$ref": "#/definitions/RuleSetConfigData"
},

View File

@@ -223,7 +223,7 @@ export class SubredditResources {
protected useSubredditAuthorCache!: boolean;
protected authorTTL: number | false = cacheTTLDefaults.authorTTL;
protected subredditTTL: number | false = cacheTTLDefaults.subredditTTL;
protected wikiTTL: number | false = cacheTTLDefaults.wikiTTL;
public wikiTTL: number | false = cacheTTLDefaults.wikiTTL;
protected submissionTTL: number | false = cacheTTLDefaults.submissionTTL;
protected commentTTL: number | false = cacheTTLDefaults.commentTTL;
protected filterCriteriaTTL: number | false = cacheTTLDefaults.filterCriteriaTTL;