Compare commits

..

5 Commits
0.9.3 ... 0.9.4

Author SHA1 Message Date
FoxxMD
97906281e6 Merge branch 'edge' 2021-11-01 14:55:10 -04:00
FoxxMD
d5e1cdec61 fix(criteria): Improve criteria filtering for removed/deleted activities and logging
* Use different logging messages when criteria is not available due to mod permissions (property not available to non-mods)
* Change logging level for missing/unavailable criteria to reduce logging noise. On unavailable use debug, on missing use warn
* Improve activity removed/deleted detection based on whether activity is moddable by current user
2021-11-01 13:25:54 -04:00
FoxxMD
ef40c25b09 feat(attribution): Add additional subreddit and activity filtering functionality to criteria
* Refactor subreddit filtering with include/exclude to use subreddit state
* Add submissionState and commentState filters
2021-11-01 11:26:55 -04:00
FoxxMD
d8180299ea fix(author): Fix missing true return statement for author flair check 2021-11-01 10:23:22 -04:00
FoxxMD
56c007c20d feat(author): Implement author profile description regex/string testing
May test "description" in authorIs as a regular express of string literal -- or as an array of the aforementioned values
2021-10-20 19:52:46 -04:00
11 changed files with 352 additions and 68 deletions

View File

@@ -1,5 +1,6 @@
import {UserNoteCriteria} from "../Rule";
import {CompareValue, CompareValueOrPercent, DurationComparor} from "../Common/interfaces";
import {parseStringToRegex} from "../util";
/**
* If present then these Author criteria are checked before running the rule. If criteria fails then the rule is skipped.
@@ -106,6 +107,17 @@ export interface AuthorCriteria {
* This is determined by trying to retrieve the author's profile. If a 404 is returned it is likely they are shadowbanned
* */
shadowBanned?: boolean
/**
* An (array of) string/regular expression to test contents of an Author's profile description against
*
* If no flags are specified then the **insensitive** flag is used by default
*
* If using an array then if **any** value in the array passes the description test passes
*
* @examples [["/test$/i", "look for this string literal"]]
* */
description?: string | string[]
}
export class Author implements AuthorCriteria {
@@ -120,6 +132,7 @@ export class Author implements AuthorCriteria {
totalKarma?: string;
verified?: boolean;
shadowBanned?: boolean;
description?: string[];
constructor(options: AuthorCriteria) {
this.name = options.name;
@@ -132,6 +145,7 @@ export class Author implements AuthorCriteria {
this.linkKarma = options.linkKarma;
this.totalKarma = options.totalKarma;
this.shadowBanned = options.shadowBanned;
this.description = options.description === undefined ? undefined : Array.isArray(options.description) ? options.description : [options.description];
}
}

View File

@@ -932,6 +932,9 @@ export interface SubmissionState extends ActivityState {
link_flair_css_class?: string
}
// properties calculated/derived by CM -- not provided as plain values by reddit
export const cmActivityProperties = ['submissionState','score','reports','removed','deleted','filtered','age','title'];
/**
* Different attributes a `Comment` can be in. Only include a property if you want to check it.
* @examples [{"op": true, "removed": false}]

View File

@@ -1,12 +1,12 @@
import {SubmissionRule, SubmissionRuleJSONConfig} from "./SubmissionRule";
import {ActivityWindowType, DomainInfo, ReferenceSubmission} from "../Common/interfaces";
import {ActivityWindowType, CommentState, DomainInfo, ReferenceSubmission, SubmissionState} from "../Common/interfaces";
import {Rule, RuleOptions, RuleResult} from "./index";
import Submission from "snoowrap/dist/objects/Submission";
import {getAttributionIdentifier} from "../Utils/SnoowrapUtils";
import dayjs from "dayjs";
import {
asSubmission,
comparisonTextOp,
comparisonTextOp, convertSubredditsRawToStrong,
FAIL,
formatNumber, getActivitySubredditName, isSubmission,
parseGenericValueOrPercentComparison,
@@ -15,6 +15,7 @@ import {
} from "../util";
import { Comment } from "snoowrap/dist/objects";
import SimpleError from "../Utils/SimpleError";
import as from "async";
export interface AttributionCriteria {
@@ -76,25 +77,41 @@ export interface AttributionCriteria {
domainsCombined?: boolean,
/**
* Only include Activities from this list of Subreddits (by name, case-insensitive)
* When present, Activities WILL ONLY be counted if they are found in this list of Subreddits
*
* Each value in the list can be either:
*
* EX `["mealtimevideos","askscience"]`
* @examples ["mealtimevideos","askscience"]
* @minItems 1
* * string (name of subreddit)
* * regular expression to run on the subreddit name
* * `SubredditState`
*
* EX `["mealtimevideos","askscience", "/onlyfans*\/i", {"over18": true}]`
* @examples [["mealtimevideos","askscience", "/onlyfans*\/i", {"over18": true}]]
* */
include?: string[],
/**
* Do not include Activities from this list of Subreddits (by name, case-insensitive)
* When present, Activities WILL NOT be counted if they are found in this list of Subreddits
*
* Will be ignored if `include` is present.
* Each value in the list can be either:
*
* EX `["mealtimevideos","askscience"]`
* @examples ["mealtimevideos","askscience"]
* @minItems 1
* * string (name of subreddit)
* * regular expression to run on the subreddit name
* * `SubredditState`
*
* EX `["mealtimevideos","askscience", "/onlyfans*\/i", {"over18": true}]`
* @examples [["mealtimevideos","askscience", "/onlyfans*\/i", {"over18": true}]]
* */
exclude?: string[],
/**
* When present, Submissions from `window` will only be counted if they meet this criteria
* */
submissionState?: SubmissionState
/**
* When present, Comments from `window` will only be counted if they meet this criteria
* */
commentState?: CommentState
/**
* This list determines which categories of domains should be aggregated on. All aggregated domains will be tested against `threshold`
*
@@ -178,21 +195,36 @@ export class AttributionRule extends Rule {
consolidateMediaDomains = false,
domains = [],
domainsCombined = false,
include: includeRaw = [],
exclude: excludeRaw = [],
include = [],
exclude = [],
commentState,
submissionState,
} = criteria;
const include = includeRaw.map(x => parseSubredditName(x).toLowerCase());
const exclude = excludeRaw.map(x => parseSubredditName(x).toLowerCase());
const {operator, value, isPercent, extra = ''} = parseGenericValueOrPercentComparison(threshold);
let activities = thresholdOn === 'submissions' ? await this.resources.getAuthorSubmissions(item.author, {window: window}) : await this.resources.getAuthorActivities(item.author, {window: window});
activities = activities.filter(act => {
if (include.length > 0) {
return include.some(x => x === getActivitySubredditName(act).toLowerCase());
} else if (exclude.length > 0) {
return !exclude.some(x => x === getActivitySubredditName(act).toLowerCase())
if(include.length > 0 || exclude.length > 0) {
const defaultOpts = {
defaultFlags: 'i',
generateDescription: true
};
if(include.length > 0) {
const subStates = include.map(x => convertSubredditsRawToStrong(x, defaultOpts));
activities = await this.resources.batchTestSubredditCriteria(activities, subStates);
} else {
const subStates = exclude.map(x => convertSubredditsRawToStrong(x, defaultOpts));
const toExclude = (await this.resources.batchTestSubredditCriteria(activities, subStates)).map(x => x.id);
activities = activities.filter(x => !toExclude.includes(x.id));
}
}
activities = await as.filter(activities, async (activity) => {
if (asSubmission(activity) && submissionState !== undefined) {
return await this.resources.testItemCriteria(activity, [submissionState]);
} else if (commentState !== undefined) {
return await this.resources.testItemCriteria(activity, [commentState]);
}
return true;
});

View File

@@ -10,7 +10,7 @@ import {
asSubmission, bitsToHexLength,
// blockHashImage,
compareImages,
comparisonTextOp,
comparisonTextOp, convertSubredditsRawToStrong,
FAIL,
formatNumber,
getActivitySubredditName, imageCompareMaxConcurrencyGuess,
@@ -284,15 +284,11 @@ export class RecentActivityRule extends Rule {
} = triggerSet;
// convert subreddits array into entirely StrongSubredditState
const subStates: StrongSubredditState[] = subreddits.map((x) => {
if (typeof x === 'string') {
return toStrongSubredditState({name: x, stateDescription: x}, {
defaultFlags: 'i',
generateDescription: true
});
}
return toStrongSubredditState(x, {defaultFlags: 'i', generateDescription: true});
});
const defaultOpts = {
defaultFlags: 'i',
generateDescription: true
};
const subStates: StrongSubredditState[] = subreddits.map((x) => convertSubredditsRawToStrong(x, defaultOpts));
let validActivity: (Comment | Submission)[] = await as.filter(viableActivity, async (activity) => {
if (asSubmission(activity) && submissionState !== undefined) {

View File

@@ -29,6 +29,26 @@
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"description": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"description": "An (array of) string/regular expression to test contents of an Author's profile description against\n\nIf no flags are specified then the **insensitive** flag is used by default\n\nIf using an array then if **any** value in the array passes the description test passes",
"examples": [
[
"/test$/i",
"look for this string literal"
]
]
},
"flairCssClass": {
"description": "A list of (user) flair css class values from the subreddit to match against",
"examples": [

View File

@@ -249,6 +249,16 @@
},
"type": "array"
},
"commentState": {
"$ref": "#/definitions/CommentState",
"description": "When present, Comments from `window` will only be counted if they meet this criteria",
"examples": [
{
"op": true,
"removed": false
}
]
},
"consolidateMediaDomains": {
"default": false,
"description": "Should the criteria consolidate recognized media domains into the parent domain?\n\nSubmissions to major media domains (youtube, vimeo) can be identified by individual Channel/Author...\n\n* If `false` then domains will be aggregated at the channel level IE Youtube Channel A (2 counts), Youtube Channel B (3 counts)\n* If `true` then then media domains will be consolidated at domain level and then aggregated IE youtube.com (5 counts)",
@@ -277,27 +287,37 @@
"type": "boolean"
},
"exclude": {
"description": "Do not include Activities from this list of Subreddits (by name, case-insensitive)\n\nWill be ignored if `include` is present.\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL NOT be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"include": {
"description": "Only include Activities from this list of Subreddits (by name, case-insensitive)\n\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL ONLY be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"minActivityCount": {
@@ -308,6 +328,16 @@
"name": {
"type": "string"
},
"submissionState": {
"$ref": "#/definitions/SubmissionState",
"description": "When present, Submissions from `window` will only be counted if they meet this criteria",
"examples": [
{
"over_18": true,
"removed": false
}
]
},
"threshold": {
"default": "> 10%",
"description": "A string containing a comparison operator and a value to compare comments against\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities originate from same attribution\n* EX `<= 10%` => less than 10% of all Activities have the same attribution",
@@ -454,6 +484,26 @@
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"description": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"description": "An (array of) string/regular expression to test contents of an Author's profile description against\n\nIf no flags are specified then the **insensitive** flag is used by default\n\nIf using an array then if **any** value in the array passes the description test passes",
"examples": [
[
"/test$/i",
"look for this string literal"
]
]
},
"flairCssClass": {
"description": "A list of (user) flair css class values from the subreddit to match against",
"examples": [

View File

@@ -195,6 +195,16 @@
},
"type": "array"
},
"commentState": {
"$ref": "#/definitions/CommentState",
"description": "When present, Comments from `window` will only be counted if they meet this criteria",
"examples": [
{
"op": true,
"removed": false
}
]
},
"consolidateMediaDomains": {
"default": false,
"description": "Should the criteria consolidate recognized media domains into the parent domain?\n\nSubmissions to major media domains (youtube, vimeo) can be identified by individual Channel/Author...\n\n* If `false` then domains will be aggregated at the channel level IE Youtube Channel A (2 counts), Youtube Channel B (3 counts)\n* If `true` then then media domains will be consolidated at domain level and then aggregated IE youtube.com (5 counts)",
@@ -223,27 +233,37 @@
"type": "boolean"
},
"exclude": {
"description": "Do not include Activities from this list of Subreddits (by name, case-insensitive)\n\nWill be ignored if `include` is present.\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL NOT be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"include": {
"description": "Only include Activities from this list of Subreddits (by name, case-insensitive)\n\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL ONLY be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"minActivityCount": {
@@ -254,6 +274,16 @@
"name": {
"type": "string"
},
"submissionState": {
"$ref": "#/definitions/SubmissionState",
"description": "When present, Submissions from `window` will only be counted if they meet this criteria",
"examples": [
{
"over_18": true,
"removed": false
}
]
},
"threshold": {
"default": "> 10%",
"description": "A string containing a comparison operator and a value to compare comments against\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities originate from same attribution\n* EX `<= 10%` => less than 10% of all Activities have the same attribution",
@@ -400,6 +430,26 @@
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"description": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"description": "An (array of) string/regular expression to test contents of an Author's profile description against\n\nIf no flags are specified then the **insensitive** flag is used by default\n\nIf using an array then if **any** value in the array passes the description test passes",
"examples": [
[
"/test$/i",
"look for this string literal"
]
]
},
"flairCssClass": {
"description": "A list of (user) flair css class values from the subreddit to match against",
"examples": [

View File

@@ -172,6 +172,16 @@
},
"type": "array"
},
"commentState": {
"$ref": "#/definitions/CommentState",
"description": "When present, Comments from `window` will only be counted if they meet this criteria",
"examples": [
{
"op": true,
"removed": false
}
]
},
"consolidateMediaDomains": {
"default": false,
"description": "Should the criteria consolidate recognized media domains into the parent domain?\n\nSubmissions to major media domains (youtube, vimeo) can be identified by individual Channel/Author...\n\n* If `false` then domains will be aggregated at the channel level IE Youtube Channel A (2 counts), Youtube Channel B (3 counts)\n* If `true` then then media domains will be consolidated at domain level and then aggregated IE youtube.com (5 counts)",
@@ -200,27 +210,37 @@
"type": "boolean"
},
"exclude": {
"description": "Do not include Activities from this list of Subreddits (by name, case-insensitive)\n\nWill be ignored if `include` is present.\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL NOT be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"include": {
"description": "Only include Activities from this list of Subreddits (by name, case-insensitive)\n\n\nEX `[\"mealtimevideos\",\"askscience\"]`",
"description": "When present, Activities WILL ONLY be counted if they are found in this list of Subreddits\n\nEach value in the list can be either:\n\n * string (name of subreddit)\n * regular expression to run on the subreddit name\n * `SubredditState`\n\nEX `[\"mealtimevideos\",\"askscience\", \"/onlyfans*\\/i\", {\"over18\": true}]`",
"examples": [
"mealtimevideos",
"askscience"
[
"mealtimevideos",
"askscience",
"/onlyfans*/i",
{
"over18": true
}
]
],
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"minActivityCount": {
@@ -231,6 +251,16 @@
"name": {
"type": "string"
},
"submissionState": {
"$ref": "#/definitions/SubmissionState",
"description": "When present, Submissions from `window` will only be counted if they meet this criteria",
"examples": [
{
"over_18": true,
"removed": false
}
]
},
"threshold": {
"default": "> 10%",
"description": "A string containing a comparison operator and a value to compare comments against\n\nThe syntax is `(< OR > OR <= OR >=) <number>[percent sign]`\n\n* EX `> 12` => greater than 12 activities originate from same attribution\n* EX `<= 10%` => less than 10% of all Activities have the same attribution",
@@ -377,6 +407,26 @@
"pattern": "^\\s*(>|>=|<|<=)\\s*(\\d+)\\s*(%?)(.*)$",
"type": "string"
},
"description": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"description": "An (array of) string/regular expression to test contents of an Author's profile description against\n\nIf no flags are specified then the **insensitive** flag is used by default\n\nIf using an array then if **any** value in the array passes the description test passes",
"examples": [
[
"/test$/i",
"look for this string literal"
]
]
},
"flairCssClass": {
"description": "A list of (user) flair css class values from the subreddit to match against",
"examples": [

View File

@@ -37,7 +37,10 @@ import {
ActionedEvent,
SubredditState,
StrongSubredditState,
HistoricalStats, HistoricalStatUpdateData, SubredditHistoricalStats, SubredditHistoricalStatsDisplay
HistoricalStats,
HistoricalStatUpdateData,
SubredditHistoricalStats,
SubredditHistoricalStatsDisplay,
} from "../Common/interfaces";
import UserNotes from "./UserNotes";
import Mustache from "mustache";
@@ -818,6 +821,10 @@ export class SubredditResources {
}
break;
case 'reports':
if (!item.can_mod_post) {
log.debug(`Cannot test for reports on Activity in a subreddit bot account is not a moderato Activist. Skipping criteria...`);
break;
}
const reportCompare = parseGenericValueComparison(crit[k] as string);
if(!comparisonTextOp(item.num_reports, reportCompare.operator, reportCompare.value)) {
// @ts-ignore
@@ -842,6 +849,10 @@ export class SubredditResources {
}
break;
case 'filtered':
if (!item.can_mod_post) {
log.debug(`Cannot test for 'filtered' state on Activity in a subreddit bot account is not a moderator for. Skipping criteria...`);
break;
}
const filtered = activityIsFiltered(item);
if (filtered !== crit['filtered']) {
// @ts-ignore
@@ -864,7 +875,7 @@ export class SubredditResources {
// @ts-ignore
const titleReg = crit[k] as string;
try {
if(null === item.title.match(titleReg)) {
if (null === item.title.match(titleReg)) {
// @ts-ignore
log.debug(`Failed to match title as regular expression: ${titleReg}`);
return false;
@@ -874,6 +885,19 @@ export class SubredditResources {
return false
}
break;
case 'approved':
case 'spam':
if(!item.can_mod_post) {
log.debug(`Cannot test for '${k}' state on Activity in a subreddit bot account is not a moderator for. Skipping criteria...`);
break;
}
// @ts-ignore
if (item[k] !== crit[k]) {
// @ts-ignore
log.debug(`Failed: Expected => ${k}:${crit[k]} | Found => ${k}:${item[k]}`)
return false
}
break;
default:
// @ts-ignore
if (item[k] !== undefined) {
@@ -884,7 +908,11 @@ export class SubredditResources {
return false
}
} else {
log.warn(`Tried to test for Item property '${k}' but it did not exist`);
if(!item.can_mod_post) {
log.warn(`Tried to test for Activity property '${k}' but it did not exist. This Activity is not in a subreddit the bot can mod so it may be that this property is only available to mods of that subreddit. Or the property may be misspelled.`);
} else {
log.warn(`Tried to test for Activity property '${k}' but it did not exist. Check the spelling of the property.`);
}
}
break;
}

View File

@@ -14,14 +14,14 @@ import {
} from "../Common/interfaces";
import {
compareDurationValue,
comparisonTextOp, getActivityAuthorName,
comparisonTextOp, escapeRegex, getActivityAuthorName,
isActivityWindowCriteria, isStatusError,
normalizeName,
parseDuration,
parseDurationComparison,
parseGenericValueComparison,
parseGenericValueOrPercentComparison,
parseRuleResultsToMarkdownSummary,
parseRuleResultsToMarkdownSummary, parseStringToRegex,
parseSubredditName,
truncateStringToLength
} from "../util";
@@ -377,7 +377,7 @@ export const testAuthorCriteria = async (item: (Comment | Submission), authorOpt
// @ts-ignore
for (const c of authorOpts[k]) {
if (c === css) {
return;
return true;
}
}
return false;
@@ -393,7 +393,7 @@ export const testAuthorCriteria = async (item: (Comment | Submission), authorOpt
// @ts-ignore
for (const c of authorOpts[k]) {
if (c === text) {
return
return true;
}
}
return false;
@@ -463,6 +463,28 @@ export const testAuthorCriteria = async (item: (Comment | Submission), authorOpt
return false;
}
break;
case 'description':
// @ts-ignore
const desc = await item.author.subreddit?.display_name.public_description;
const dVals = authorOpts[k] as string[];
let passed = false;
for(const val of dVals) {
let reg = parseStringToRegex(val, 'i');
if(reg === undefined) {
reg = parseStringToRegex(`/.*${escapeRegex(val.trim())}.*/`, 'i');
if(reg === undefined) {
throw new SimpleError(`Could not convert 'description' value to a valid regex: ${authorOpts[k] as string}`);
}
}
if(reg.test(desc)) {
passed = true;
break;
}
}
if(!passed) {
return false;
}
break;
case 'userNotes':
const notes = await userNotes.getUserNotes(item.author);
const notePass = () => {
@@ -661,13 +683,21 @@ export const getAttributionIdentifier = (sub: Submission, useParentMediaDomain =
}
export const activityIsRemoved = (item: Submission | Comment): boolean => {
if (item instanceof Submission) {
// when automod filters a post it gets this category
return item.banned_at_utc !== null && item.removed_by_category !== 'automod_filtered';
if(item.can_mod_post) {
if (item instanceof Submission) {
// when automod filters a post it gets this category
return item.banned_at_utc !== null && item.removed_by_category !== 'automod_filtered';
}
// when automod filters a comment item.removed === false
// so if we want to processing filtered comments we need to check for this
return item.banned_at_utc !== null && item.removed;
} else {
if (item instanceof Submission) {
return item.removed_by_category === 'moderator' || item.removed_by_category === 'deleted';
}
// in subreddits the bot does not mod it is not possible to tell the difference between a comment that was removed by the user and one that was removed by a mod
return item.body === '[removed]';
}
// when automod filters a comment item.removed === false
// so if we want to processing filtered comments we need to check for this
return item.banned_at_utc !== null && item.removed;
}
export const activityIsFiltered = (item: Submission | Comment): boolean => {

View File

@@ -969,6 +969,13 @@ export const toStrongSubredditState = (s: SubredditState, opts?: StrongSubreddit
return strongState;
}
export const convertSubredditsRawToStrong = (x: (SubredditState | string), opts: StrongSubredditStateOptions): StrongSubredditState => {
if (typeof x === 'string') {
return toStrongSubredditState({name: x, stateDescription: x}, opts);
}
return toStrongSubredditState(x, opts);
}
export async function readConfigFile(path: string, opts: any) {
const {log, throwOnNotFound = true} = opts;
try {
@@ -1413,3 +1420,7 @@ export const absPercentDifference = (num1: number, num2: number) => {
export const bitsToHexLength = (bits: number): number => {
return Math.pow(bits, 2) / 4;
}
export const escapeRegex = (val: string) => {
return val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}