mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-01-15 00:18:06 -05:00
Compare commits
54 Commits
redisCache
...
0.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d02d70ded3 | ||
|
|
acbb9a8626 | ||
|
|
122d5fb2af | ||
|
|
80f83bf84b | ||
|
|
7933f77764 | ||
|
|
3bcc3d78e8 | ||
|
|
296f1c8dee | ||
|
|
e32ac60db5 | ||
|
|
859680dca8 | ||
|
|
ffa1e423b2 | ||
|
|
09cb08492c | ||
|
|
d9ab81ab8c | ||
|
|
98691bd19c | ||
|
|
8123c34463 | ||
|
|
3292d011fa | ||
|
|
661a0ae440 | ||
|
|
05f477b67d | ||
|
|
1317a5916c | ||
|
|
e9135ec1ef | ||
|
|
e58a0f8f21 | ||
|
|
f7cebc013b | ||
|
|
ae8e11feb4 | ||
|
|
e07b8cc291 | ||
|
|
fc51928054 | ||
|
|
e2590e50f8 | ||
|
|
aaed0d3419 | ||
|
|
bc7eff8928 | ||
|
|
d6954533a0 | ||
|
|
ba53233640 | ||
|
|
1ac7ad4724 | ||
|
|
2a282a0d6f | ||
|
|
fd5a92758d | ||
|
|
39daa11f2d | ||
|
|
dac6541e28 | ||
|
|
97906281e6 | ||
|
|
487f13f704 | ||
|
|
631e21452c | ||
|
|
4f3685a1f5 | ||
|
|
d2d945db2c | ||
|
|
910f7f79ef | ||
|
|
a11b667d5e | ||
|
|
885e3fa765 | ||
|
|
465c3c9acf | ||
|
|
161251a943 | ||
|
|
ce4cb96d9a | ||
|
|
c317f95953 | ||
|
|
d0e0515990 | ||
|
|
cdddd8de48 | ||
|
|
f598215d88 | ||
|
|
0c7218571c | ||
|
|
acc7c49e0e | ||
|
|
01839512d5 | ||
|
|
4680640b0c | ||
|
|
b813ebdd96 |
@@ -754,7 +754,7 @@ actions:
|
||||
- kind: usernote
|
||||
type: spamwarn
|
||||
content: 'Usernote message'
|
||||
allowDuplicate: boolean # if false then the usernote will not be added if the same note appears for this activity
|
||||
existingNoteCheck: boolean # if true (default) then the usernote will not be added if the same note appears for this activity
|
||||
```
|
||||
|
||||
### Mod Note
|
||||
@@ -779,6 +779,7 @@ actions:
|
||||
type: SPAM_WATCH
|
||||
content: 'a note only mods can see message' # optional
|
||||
referenceActivity: boolean # if true the Note will be linked to the Activity being processed
|
||||
existingNoteCheck: boolean # if true (default) then the note will not be added if the same note appears for this activity
|
||||
```
|
||||
|
||||
# Filters
|
||||
|
||||
@@ -10,5 +10,5 @@ Consult the [schema](https://json-schema.app/view/%23/%23%2Fdefinitions%2FCheckJ
|
||||
|
||||
### Examples
|
||||
|
||||
* Self Promotion as percentage of all Activities [YAML](/docs/subreddit/componentscomponents/attribution/redditSelfPromoAll.yaml) | [JSON](/docs/subreddit/componentscomponents/attribution/redditSelfPromoAll.json5) - Check if Author is submitting much more than they comment.
|
||||
* Self Promotion as percentage of all Activities [YAML](/docs/subreddit/components/attribution/redditSelfPromoAll.yaml) | [JSON](/docs/subreddit/components/attribution/redditSelfPromoAll.json5) - Check if Author is submitting much more than they comment.
|
||||
* Self Promotion as percentage of Submissions [YAML](/docs/subreddit/components/attribution/redditSelfPromoSubmissionsOnly.yaml) | [JSON](/docs/examplesm/attribution/redditSelfPromoSubmissionsOnly.json5) - Check if any of Author's aggregated submission origins are >10% of their submissions
|
||||
|
||||
@@ -9,7 +9,7 @@ The **Author** rule triggers if any [AuthorCriteria](https://json-schema.app/vie
|
||||
* author's subreddit flair text
|
||||
* author's subreddit flair css
|
||||
* author's subreddit mod status
|
||||
* [Toolbox User Notes](/docs/subreddit/componentscomponents/userNotes)
|
||||
* [Toolbox User Notes](/docs/subreddit/components/userNotes)
|
||||
|
||||
The Author **Rule** is best used in conjunction with other Rules to short-circuit a Check based on who the Author is. It is easier to use a Rule to do this then to write **author filters** for every Rule (and makes Rules more re-useable).
|
||||
|
||||
@@ -18,10 +18,10 @@ Consult the [schema](https://json-schema.app/view/%23%2Fdefinitions%2FAuthorRule
|
||||
### Examples
|
||||
|
||||
* Basic examples
|
||||
* Flair new user Submission [YAML](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.json5) - If the Author does not have the `vet` flair then flair the Submission with `New User`
|
||||
* Flair vetted user Submission [YAML](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.json5) - If the Author does have the `vet` flair then flair the Submission with `Vetted`
|
||||
* Flair new user Submission [YAML](/docs/subreddit/components/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/components/author/flairNewUserSubmission.json5) - If the Author does not have the `vet` flair then flair the Submission with `New User`
|
||||
* Flair vetted user Submission [YAML](/docs/subreddit/components/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/components/author/flairNewUserSubmission.json5) - If the Author does have the `vet` flair then flair the Submission with `Vetted`
|
||||
* Used with other Rules
|
||||
* Ignore vetted user [YAML](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/componentscomponents/author/flairNewUserSubmission.json5) - Short-circuit the Check if the Author has the `vet` flair
|
||||
* Ignore vetted user [YAML](/docs/subreddit/components/author/flairNewUserSubmission.yaml) | [JSON](/docs/subreddit/components/author/flairNewUserSubmission.json5) - Short-circuit the Check if the Author has the `vet` flair
|
||||
|
||||
## Filter
|
||||
|
||||
@@ -35,7 +35,7 @@ All **Rules** and **Checks** have an optional `authorIs` property that takes an
|
||||
|
||||
### Examples
|
||||
|
||||
* Skip recent activity check based on author [YAML](/docs/subreddit/componentscomponents/author/authorFilter.yaml) | [JSON](/docs/subreddit/componentscomponents/author/authorFilter.json5) - Skip a Recent Activity check for a set of subreddits if the Author of the Submission has any set of flairs.
|
||||
* Skip recent activity check based on author [YAML](/docs/subreddit/components/author/authorFilter.yaml) | [JSON](/docs/subreddit/components/author/authorFilter.json5) - Skip a Recent Activity check for a set of subreddits if the Author of the Submission has any set of flairs.
|
||||
|
||||
## Flair users and submissions
|
||||
|
||||
@@ -45,4 +45,4 @@ Consult [User Flair schema](https://json-schema.app/view/%23%2Fdefinitions%2FUse
|
||||
|
||||
### Examples
|
||||
|
||||
* OnlyFans submissions [YAML](/docs/subreddit/componentscomponents/author/onlyfansFlair.yaml) | [JSON](/docs/subreddit/componentscomponents/author/onlyfansFlair.json5) - Check whether submitter has typical OF keywords in their profile and flair both author + submission accordingly.
|
||||
* OnlyFans submissions [YAML](/docs/subreddit/components/author/onlyfansFlair.yaml) | [JSON](/docs/subreddit/components/author/onlyfansFlair.json5) - Check whether submitter has typical OF keywords in their profile and flair both author + submission accordingly.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
// for this to pass the Author of the Submission must not have the flair "Supreme Memer" and have the name "user1" or "user2"
|
||||
{
|
||||
"flairText": ["Supreme Memer"],
|
||||
"names": ["user1","user2"]
|
||||
"name": ["user1","user2"]
|
||||
},
|
||||
{
|
||||
// for this to pass the Author of the Submission must not have the flair "Decent Memer"
|
||||
|
||||
@@ -30,7 +30,7 @@ runs:
|
||||
# for this to pass the Author of the Submission must not have the flair "Supreme Memer" and have the name "user1" or "user2"
|
||||
- flairText:
|
||||
- Supreme Memer
|
||||
names:
|
||||
name:
|
||||
- user1
|
||||
- user2
|
||||
# for this to pass the Author of the Submission must not have the flair "Decent Memer"
|
||||
|
||||
@@ -46,5 +46,5 @@ Example:
|
||||
|
||||
### Examples
|
||||
|
||||
* Low Comment Engagement [YAML](/docs/subreddit/componentscomponents/history/lowEngagement.yaml) | [JSON](/docs/subreddit/componentscomponents/history/lowEngagement.json5) - Check if Author is submitting much more than they comment.
|
||||
* OP Comment Engagement [YAML](/docs/subreddit/componentscomponents/history/opOnlyEngagement.yaml) | [JSON](/docs/subreddit/componentscomponents/history/opOnlyEngagement.json5) - Check if Author is mostly engaging only in their own content
|
||||
* Low Comment Engagement [YAML](/docs/subreddit/components/history/lowEngagement.yaml) | [JSON](/docs/subreddit/components/history/lowEngagement.json5) - Check if Author is submitting much more than they comment.
|
||||
* OP Comment Engagement [YAML](/docs/subreddit/components/history/opOnlyEngagement.yaml) | [JSON](/docs/subreddit/components/history/opOnlyEngagement.json5) - Check if Author is mostly engaging only in their own content
|
||||
|
||||
@@ -27,5 +27,5 @@ Consult the [schema](https://json-schema.app/view/%23%2Fdefinitions%2FRecentActi
|
||||
|
||||
### Examples
|
||||
|
||||
* Free Karma Subreddits [YAML](/docs/subreddit/componentscomponents/recentActivity/freeKarma.yaml) | [JSON](/docs/subreddit/componentscomponents/recentActivity/freeKarma.json5) - Check if the Author has recently posted in any "free karma" subreddits
|
||||
* Submission in Free Karma Subreddits [YAML](/docs/subreddit/componentscomponents/recentActivity/freeKarmaOnSubmission.yaml) | [JSON](/docs/subreddit/componentscomponents/recentActivity/freeKarmaOnSubmission.json5) - Check if the Author has posted the Submission this check is running on in any "free karma" subreddits recently
|
||||
* Free Karma Subreddits [YAML](/docs/subreddit/components/recentActivity/freeKarma.yaml) | [JSON](/docs/subreddit/components/recentActivity/freeKarma.json5) - Check if the Author has recently posted in any "free karma" subreddits
|
||||
* Submission in Free Karma Subreddits [YAML](/docs/subreddit/components/recentActivity/freeKarmaOnSubmission.yaml) | [JSON](/docs/subreddit/components/recentActivity/freeKarmaOnSubmission.json5) - Check if the Author has posted the Submission this check is running on in any "free karma" subreddits recently
|
||||
|
||||
@@ -11,12 +11,12 @@ Which can then be used in conjunction with a [`window`](https://github.com/FoxxM
|
||||
|
||||
### Examples
|
||||
|
||||
* Trigger if regex matches against the current activity - [YAML](/docs/subreddit/componentscomponents/regex/matchAnyCurrentActivity.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchAnyCurrentActivity.json5)
|
||||
* Trigger if regex matches 5 times against the current activity - [YAML](/docs/subreddit/componentscomponents/regex/matchThresholdCurrentActivity.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchThresholdCurrentActivity.json5)
|
||||
* Trigger if regex matches against any part of a Submission - [YAML](/docs/subreddit/componentscomponents/regex/matchSubmissionParts.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchSubmissionParts.json5)
|
||||
* Trigger if regex matches any of Author's last 10 activities - [YAML](/docs/subreddit/componentscomponents/regex/matchHistoryActivity.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchHistoryActivity.json5)
|
||||
* Trigger if regex matches at least 3 of Author's last 10 activities - [YAML](/docs/subreddit/componentscomponents/regex/matchActivityThresholdHistory.json5) | [JSON](/docs/subreddit/componentscomponents/regex/matchActivityThresholdHistory.json5)
|
||||
* Trigger if there are 5 regex matches in the Author's last 10 activities - [YAML](/docs/subreddit/componentscomponents/regex/matchTotalHistoryActivity.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchTotalHistoryActivity.json5)
|
||||
* Trigger if there are 5 regex matches in the Author's last 10 comments - [YAML](/docs/subreddit/componentscomponents/regex/matchSubsetHistoryActivity.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/matchSubsetHistoryActivity.json5)
|
||||
* Remove comments that are spamming discord links - [YAML](/docs/subreddit/componentscomponents/regex/removeDiscordSpam.yaml) | [JSON](/docs/subreddit/componentscomponents/regex/removeDiscordSpam.json5)
|
||||
* Trigger if regex matches against the current activity - [YAML](/docs/subreddit/components/regex/matchAnyCurrentActivity.yaml) | [JSON](/docs/subreddit/components/regex/matchAnyCurrentActivity.json5)
|
||||
* Trigger if regex matches 5 times against the current activity - [YAML](/docs/subreddit/components/regex/matchThresholdCurrentActivity.yaml) | [JSON](/docs/subreddit/components/regex/matchThresholdCurrentActivity.json5)
|
||||
* Trigger if regex matches against any part of a Submission - [YAML](/docs/subreddit/components/regex/matchSubmissionParts.yaml) | [JSON](/docs/subreddit/components/regex/matchSubmissionParts.json5)
|
||||
* Trigger if regex matches any of Author's last 10 activities - [YAML](/docs/subreddit/components/regex/matchHistoryActivity.yaml) | [JSON](/docs/subreddit/components/regex/matchHistoryActivity.json5)
|
||||
* Trigger if regex matches at least 3 of Author's last 10 activities - [YAML](/docs/subreddit/components/regex/matchActivityThresholdHistory.json5) | [JSON](/docs/subreddit/components/regex/matchActivityThresholdHistory.json5)
|
||||
* Trigger if there are 5 regex matches in the Author's last 10 activities - [YAML](/docs/subreddit/components/regex/matchTotalHistoryActivity.yaml) | [JSON](/docs/subreddit/components/regex/matchTotalHistoryActivity.json5)
|
||||
* Trigger if there are 5 regex matches in the Author's last 10 comments - [YAML](/docs/subreddit/components/regex/matchSubsetHistoryActivity.yaml) | [JSON](/docs/subreddit/components/regex/matchSubsetHistoryActivity.json5)
|
||||
* Remove comments that are spamming discord links - [YAML](/docs/subreddit/components/regex/removeDiscordSpam.yaml) | [JSON](/docs/subreddit/components/regex/removeDiscordSpam.json5)
|
||||
* Differs from just using automod because this config can allow one-off/organic links from users who DO NOT spam discord links but will still remove the comment if the user is spamming them
|
||||
|
||||
@@ -47,5 +47,5 @@ With only `gapAllowance: 2` this rule **would trigger** because the the 1 and 2
|
||||
|
||||
## Examples
|
||||
|
||||
* Crosspost Spamming [JSON](/docs/subreddit/componentscomponents/repeatActivity/crosspostSpamming.json5) | [YAML](/docs/subreddit/componentscomponents/repeatActivity/crosspostSpamming.yaml) - Check if an Author is spamming their Submissions across multiple subreddits
|
||||
* Burst-posting [JSON](/docs/subreddit/componentscomponents/repeatActivity/burstPosting.json5) | [YAML](/docs/subreddit/componentscomponents/repeatActivity/burstPosting.yaml) - Check if Author is crossposting their Submissions in short bursts
|
||||
* Crosspost Spamming [JSON](/docs/subreddit/components/repeatActivity/crosspostSpamming.json5) | [YAML](/docs/subreddit/components/repeatActivity/crosspostSpamming.yaml) - Check if an Author is spamming their Submissions across multiple subreddits
|
||||
* Burst-posting [JSON](/docs/subreddit/components/repeatActivity/burstPosting.json5) | [YAML](/docs/subreddit/components/repeatActivity/burstPosting.yaml) - Check if Author is crossposting their Submissions in short bursts
|
||||
|
||||
@@ -17,25 +17,25 @@ All actions for these configurations are non-destructive in that:
|
||||
|
||||
### Remove submissions from users who have used 'freekarma' subs to bypass karma checks
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/freekarma.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/freekarma.json5)
|
||||
[YAML](/docs/subreddit/components/subredditReady/freekarma.yaml) | [JSON](/docs/subreddit/components/subredditReady/freekarma.json5)
|
||||
|
||||
If the user has any activity (comment/submission) in known freekarma subreddits in the past (50 activities or 6 months) then remove the submission.
|
||||
|
||||
### Remove submissions from users who have crossposted the same submission 4 or more times
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/crosspostSpam.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/crosspostSpam.yaml)
|
||||
[YAML](/docs/subreddit/components/subredditReady/crosspostSpam.yaml) | [JSON](/docs/subreddit/components/subredditReady/crosspostSpam.yaml)
|
||||
|
||||
If the user has crossposted the same submission in the past (50 activities or 6 months) 4 or more times in a row then remove the submission.
|
||||
|
||||
### Remove submissions from users who have crossposted or used 'freekarma' subs
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/freeKarmaOrCrosspostSpam.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/freeKarmaOrCrosspostSpam.json5)
|
||||
[YAML](/docs/subreddit/componentsc/subredditReady/freeKarmaOrCrosspostSpam.yaml) | [JSON](/docs/subreddit/components/subredditReady/freeKarmaOrCrosspostSpam.json5)
|
||||
|
||||
Will remove submission if either of the above two behaviors is detected
|
||||
|
||||
### Remove link submissions where the user's history is comprised of 10% or more of the same link
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/selfPromo.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/selfPromo.json5)
|
||||
[YAML](/docs/subreddit/components/subredditReady/selfPromo.yaml) | [JSON](/docs/subreddit/components/subredditReady/selfPromo.json5)
|
||||
|
||||
If the link origin (youtube author, twitter author, etc. or regular domain for non-media links)
|
||||
|
||||
@@ -48,13 +48,13 @@ then remove the submission
|
||||
|
||||
### Remove comment if the user has posted the same comment 4 or more times in a row
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/commentSpam.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/commentSpam.json5)
|
||||
[YAML](/docs/subreddit/components/subredditReady/commentSpam.yaml) | [JSON](/docs/subreddit/components/subredditReady/commentSpam.json5)
|
||||
|
||||
If the user made the same comment (with some fuzzy matching) 4 or more times in a row in the past (50 activities or 6 months) then remove the comment.
|
||||
|
||||
### Remove comment if it is discord invite link spam
|
||||
|
||||
[YAML](/docs/subreddit/componentscomponents/subredditReady/discordSpam.yaml) | [JSON](/docs/subreddit/componentscomponents/subredditReady/discordSpam.json5)
|
||||
[YAML](/docs/subreddit/components/subredditReady/discordSpam.yaml) | [JSON](/docs/subreddit/components/subredditReady/discordSpam.json5)
|
||||
|
||||
This rule goes a step further than automod can by being more discretionary about how it handles this type of spam.
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Consult the [schema](https://json-schema.app/view/%23%2Fdefinitions%2FUserNoteCr
|
||||
|
||||
### Examples
|
||||
|
||||
* Do not tag user with Good User note [JSON](/docs/subreddit/componentscomponents/userNotes/usernoteFilter.json5) | [YAML](/docs/subreddit/componentscomponents/userNotes/usernoteFilter.yaml)
|
||||
* Do not tag user with Good User note [JSON](/docs/subreddit/components/userNotes/usernoteFilter.json5) | [YAML](/docs/subreddit/components/userNotes/usernoteFilter.yaml)
|
||||
|
||||
## Action
|
||||
|
||||
@@ -33,4 +33,4 @@ A User Note can also be added to the Author of a Submission or Comment with the
|
||||
|
||||
### Examples
|
||||
|
||||
* Add note on user doing self promotion [JSON](/docs/subreddit/componentscomponents/userNotes/usernoteSP.json5) | [YAML](/docs/subreddit/componentscomponents/userNotes/usernoteSP.yaml)
|
||||
* Add note on user doing self promotion [JSON](/docs/subreddit/components/userNotes/usernoteSP.json5) | [YAML](/docs/subreddit/components/userNotes/usernoteSP.yaml)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {ActionJson, ActionConfig, ActionOptions} from "./index";
|
||||
import Action from "./index";
|
||||
import {Comment} from "snoowrap";
|
||||
import {renderContent} from "../Utils/SnoowrapUtils";
|
||||
import {UserNoteJson} from "../Subreddit/UserNotes";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import {ActionProcessResult, RuleResult} from "../Common/interfaces";
|
||||
@@ -9,19 +8,34 @@ import {RuleResultEntity} from "../Common/Entities/RuleResultEntity";
|
||||
import {runCheckOptions} from "../Subreddit/Manager";
|
||||
import {ActionTypes, UserNoteType} from "../Common/Infrastructure/Atomic";
|
||||
import {ActionResultEntity} from "../Common/Entities/ActionResultEntity";
|
||||
import {
|
||||
FullUserNoteCriteria,
|
||||
toFullUserNoteCriteria, UserNoteCriteria
|
||||
} from "../Common/Infrastructure/Filters/FilterCriteria";
|
||||
import {buildFilterCriteriaSummary} from "../util";
|
||||
|
||||
|
||||
export class UserNoteAction extends Action {
|
||||
content: string;
|
||||
type: UserNoteType;
|
||||
allowDuplicate: boolean;
|
||||
existingNoteCheck?: UserNoteCriteria
|
||||
|
||||
constructor(options: UserNoteActionOptions) {
|
||||
super(options);
|
||||
const {type, content = '', allowDuplicate = false} = options;
|
||||
const {type, content = '', existingNoteCheck = true, allowDuplicate} = options;
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
this.allowDuplicate = allowDuplicate;
|
||||
if(typeof existingNoteCheck !== 'boolean') {
|
||||
this.existingNoteCheck = existingNoteCheck;
|
||||
} else {
|
||||
let exNotecheck: boolean;
|
||||
if(allowDuplicate !== undefined) {
|
||||
exNotecheck = !allowDuplicate;
|
||||
} else {
|
||||
exNotecheck = existingNoteCheck;
|
||||
}
|
||||
this.existingNoteCheck = this.generateCriteriaFromDuplicateConvenience(exNotecheck);
|
||||
}
|
||||
}
|
||||
|
||||
getKind(): ActionTypes {
|
||||
@@ -33,25 +47,30 @@ export class UserNoteAction extends Action {
|
||||
const renderedContent = (await this.renderContent(this.content, item, ruleResults, actionResults) as string);
|
||||
this.logger.verbose(`Note:\r\n(${this.type}) ${renderedContent}`);
|
||||
|
||||
if (!this.allowDuplicate) {
|
||||
const notes = await this.resources.userNotes.getUserNotes(item.author);
|
||||
let existingNote = notes.find((x) => x.link !== null && x.link.includes(item.id));
|
||||
if(existingNote === undefined && notes.length > 0) {
|
||||
const lastNote = notes[notes.length - 1];
|
||||
// possibly notes don't have a reference link so check if last one has same text
|
||||
if(lastNote.link === null && lastNote.text === renderedContent) {
|
||||
existingNote = lastNote;
|
||||
}
|
||||
}
|
||||
if (existingNote !== undefined && existingNote.noteType === this.type) {
|
||||
this.logger.info(`Will not add note because one already exists for this Activity (${existingNote.time.local().format()}) and allowDuplicate=false`);
|
||||
return {
|
||||
dryRun,
|
||||
success: false,
|
||||
result: `Will not add note because one already exists for this Activity (${existingNote.time.local().format()}) and allowDuplicate=false`
|
||||
};
|
||||
}
|
||||
let noteCheckPassed: boolean = true;
|
||||
let noteCheckResult: undefined | string;
|
||||
|
||||
if(this.existingNoteCheck === undefined) {
|
||||
// nothing to do!
|
||||
noteCheckResult = 'existingNoteCheck=false so no existing note checks were performed.';
|
||||
} else {
|
||||
const noteCheckCriteriaResult = await this.resources.isAuthor(item, {
|
||||
userNotes: [this.existingNoteCheck]
|
||||
});
|
||||
noteCheckPassed = noteCheckCriteriaResult.passed;
|
||||
const {details} = buildFilterCriteriaSummary(noteCheckCriteriaResult);
|
||||
noteCheckResult = `${noteCheckPassed ? 'Existing note check condition succeeded' : 'Will not add note because existing note check condition failed'} -- ${details.join(' ')}`;
|
||||
}
|
||||
|
||||
this.logger.info(noteCheckResult);
|
||||
if (!noteCheckPassed) {
|
||||
return {
|
||||
dryRun,
|
||||
success: false,
|
||||
result: noteCheckResult
|
||||
};
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
await this.resources.userNotes.addUserNote(item, this.type, renderedContent, this.name !== undefined ? `(Action ${this.name})` : '');
|
||||
} else if (!await this.resources.userNotes.warningExists(this.type)) {
|
||||
@@ -64,11 +83,23 @@ export class UserNoteAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
generateCriteriaFromDuplicateConvenience(val: boolean): UserNoteCriteria | undefined {
|
||||
if(val) {
|
||||
return {
|
||||
type: this.type,
|
||||
note: this.content !== '' && this.content !== undefined && this.content !== null ? [this.content] : undefined,
|
||||
search: 'current',
|
||||
count: '< 1'
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected getSpecificPremise(): object {
|
||||
return {
|
||||
content: this.content,
|
||||
type: this.type,
|
||||
allowDuplicate: this.allowDuplicate
|
||||
existingNoteCheck: this.existingNoteCheck
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,10 +107,29 @@ export class UserNoteAction extends Action {
|
||||
export interface UserNoteActionConfig extends ActionConfig,UserNoteJson {
|
||||
/**
|
||||
* Add Note even if a Note already exists for this Activity
|
||||
*
|
||||
* USE `existingNoteCheck` INSTEAD
|
||||
*
|
||||
* @examples [false]
|
||||
* @default false
|
||||
* @deprecated
|
||||
* */
|
||||
allowDuplicate?: boolean,
|
||||
|
||||
/**
|
||||
* Check if there is an existing Note matching some criteria before adding the Note.
|
||||
*
|
||||
* If this check passes then the Note is added. The value may be a boolean or UserNoteCriteria.
|
||||
*
|
||||
* Boolean convenience:
|
||||
*
|
||||
* * If `true` or undefined then CM generates a UserNoteCriteria that passes only if there is NO existing note matching note criteria
|
||||
* * If `false` then no check is performed and Note is always added
|
||||
*
|
||||
* @examples [true]
|
||||
* @default true
|
||||
* */
|
||||
existingNoteCheck?: boolean | UserNoteCriteria,
|
||||
}
|
||||
|
||||
export interface UserNoteActionOptions extends Omit<UserNoteActionConfig, 'authorIs' | 'itemIs'>, ActionOptions {
|
||||
|
||||
@@ -119,6 +119,34 @@ export interface UserNoteCriteria extends UserSubredditHistoryCriteria {
|
||||
* @examples ["spamwarn"]
|
||||
* */
|
||||
type: string;
|
||||
/**
|
||||
* The content of the Note to search For.
|
||||
*
|
||||
* * Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.
|
||||
* * Can also be Regular Expression if wrapped in forward slashes IE '\/test.*\/i'
|
||||
* */
|
||||
note?: string | string[]
|
||||
/*
|
||||
* Does this note link to the currently processing Activity?
|
||||
* */
|
||||
referencesCurrentActivity?: boolean
|
||||
}
|
||||
|
||||
export interface FullUserNoteCriteria extends Omit<UserNoteCriteria, 'note'> {
|
||||
note?: RegExp[]
|
||||
}
|
||||
|
||||
export const toFullUserNoteCriteria = (val: UserNoteCriteria): FullUserNoteCriteria => {
|
||||
const {note} = val;
|
||||
let notesVal = undefined;
|
||||
if (note !== undefined) {
|
||||
const notesArr = Array.isArray(note) ? note : [note];
|
||||
notesVal = notesArr.map(x => parseStringToRegexOrLiteralSearch(x));
|
||||
}
|
||||
return {
|
||||
...val,
|
||||
note: notesVal
|
||||
}
|
||||
}
|
||||
|
||||
export interface ModActionCriteria extends UserSubredditHistoryCriteria {
|
||||
@@ -130,6 +158,9 @@ export interface ModActionCriteria extends UserSubredditHistoryCriteria {
|
||||
export interface FullModActionCriteria extends Omit<ModActionCriteria, 'count'> {
|
||||
type?: ModActionType[]
|
||||
count?: GenericComparison
|
||||
/*
|
||||
* Does this action/note link to the currently processing Activity?
|
||||
* */
|
||||
activityType?: MaybeActivityType[]
|
||||
}
|
||||
|
||||
@@ -140,6 +171,12 @@ export interface ModNoteCriteria extends ModActionCriteria {
|
||||
|
||||
export interface FullModNoteCriteria extends FullModActionCriteria, Omit<ModNoteCriteria, 'note' | 'count' | 'type' | 'activityType'> {
|
||||
noteType?: ModUserNoteLabel[]
|
||||
/**
|
||||
* The content of the Note to search For.
|
||||
*
|
||||
* * Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.
|
||||
* * Can also be Regular Expression if wrapped in forward slashes IE '\/test.*\/i'
|
||||
* */
|
||||
note?: RegExp[]
|
||||
}
|
||||
|
||||
|
||||
@@ -45,4 +45,4 @@ export const filterCriteriaDefault: FilterCriteriaDefaults = {
|
||||
export const defaultDataDir = path.resolve(__dirname, '../..');
|
||||
export const defaultConfigFilenames = ['config.json', 'config.yaml'];
|
||||
|
||||
export const VERSION = '0.13.1';
|
||||
export const VERSION = '0.13.2';
|
||||
|
||||
@@ -2972,7 +2972,7 @@
|
||||
"properties": {
|
||||
"allowDuplicate": {
|
||||
"default": false,
|
||||
"description": "Add Note even if a Note already exists for this Activity",
|
||||
"description": "Add Note even if a Note already exists for this Activity\n\nUSE `existingNoteCheck` INSTEAD",
|
||||
"examples": [
|
||||
false
|
||||
],
|
||||
@@ -3028,6 +3028,21 @@
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
"existingNoteCheck": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/UserNoteCriteria"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"description": "Check if there is an existing Note matching some criteria before adding the Note.\n\nIf this check passes then the Note is added. The value may be a boolean or UserNoteCriteria.\n\nBoolean convenience:\n\n* If `true` or undefined then CM generates a UserNoteCriteria that passes only if there is NO existing note matching note criteria\n* If `false` then no check is performed and Note is always added",
|
||||
"examples": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"itemIs": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -3095,6 +3110,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -6854,7 +6854,7 @@
|
||||
"properties": {
|
||||
"allowDuplicate": {
|
||||
"default": false,
|
||||
"description": "Add Note even if a Note already exists for this Activity",
|
||||
"description": "Add Note even if a Note already exists for this Activity\n\nUSE `existingNoteCheck` INSTEAD",
|
||||
"examples": [
|
||||
false
|
||||
],
|
||||
@@ -6910,6 +6910,21 @@
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
"existingNoteCheck": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/UserNoteCriteria"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"description": "Check if there is an existing Note matching some criteria before adding the Note.\n\nIf this check passes then the Note is added. The value may be a boolean or UserNoteCriteria.\n\nBoolean convenience:\n\n* If `true` or undefined then CM generates a UserNoteCriteria that passes only if there is NO existing note matching note criteria\n* If `false` then no check is performed and Note is always added",
|
||||
"examples": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"itemIs": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -6977,6 +6992,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -6270,7 +6270,7 @@
|
||||
"properties": {
|
||||
"allowDuplicate": {
|
||||
"default": false,
|
||||
"description": "Add Note even if a Note already exists for this Activity",
|
||||
"description": "Add Note even if a Note already exists for this Activity\n\nUSE `existingNoteCheck` INSTEAD",
|
||||
"examples": [
|
||||
false
|
||||
],
|
||||
@@ -6326,6 +6326,21 @@
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
"existingNoteCheck": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/UserNoteCriteria"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"description": "Check if there is an existing Note matching some criteria before adding the Note.\n\nIf this check passes then the Note is added. The value may be a boolean or UserNoteCriteria.\n\nBoolean convenience:\n\n* If `true` or undefined then CM generates a UserNoteCriteria that passes only if there is NO existing note matching note criteria\n* If `false` then no check is performed and Note is always added",
|
||||
"examples": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"itemIs": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -6393,6 +6408,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -968,8 +968,18 @@
|
||||
"credentials": {
|
||||
"$ref": "#/definitions/InfluxCredentials"
|
||||
},
|
||||
"debug": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultTags": {
|
||||
"$ref": "#/definitions/Record<string,string>"
|
||||
},
|
||||
"useKeepAliveAgent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"writeOptions": {
|
||||
"$ref": "#/definitions/WriteOptions",
|
||||
"description": "Options used by{@linkWriteApi}."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2283,6 +2293,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
@@ -2342,6 +2369,94 @@
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"WriteOptions": {
|
||||
"description": "Options used by{@linkWriteApi}.",
|
||||
"properties": {
|
||||
"batchSize": {
|
||||
"description": "max number of records/lines to send in a batch",
|
||||
"type": "number"
|
||||
},
|
||||
"consistency": {
|
||||
"description": "InfluxDB Enterprise write consistency as explained in https://docs.influxdata.com/enterprise_influxdb/v1.9/concepts/clustering/#write-consistency",
|
||||
"enum": [
|
||||
"all",
|
||||
"any",
|
||||
"one",
|
||||
"quorum"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"defaultTags": {
|
||||
"$ref": "#/definitions/Record<string,string>",
|
||||
"description": "default tags, unescaped"
|
||||
},
|
||||
"exponentialBase": {
|
||||
"description": "base for the exponential retry delay",
|
||||
"type": "number"
|
||||
},
|
||||
"flushInterval": {
|
||||
"description": "delay between data flushes in milliseconds, at most `batch size` records are sent during flush",
|
||||
"type": "number"
|
||||
},
|
||||
"gzipThreshold": {
|
||||
"description": "When specified, write bodies larger than the threshold are gzipped",
|
||||
"type": "number"
|
||||
},
|
||||
"headers": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "HTTP headers that will be sent with every write request",
|
||||
"type": "object"
|
||||
},
|
||||
"maxBatchBytes": {
|
||||
"description": "max size of a batch in bytes",
|
||||
"type": "number"
|
||||
},
|
||||
"maxBufferLines": {
|
||||
"description": "the maximum size of retry-buffer (in lines)",
|
||||
"type": "number"
|
||||
},
|
||||
"maxRetries": {
|
||||
"description": "max count of retries after the first write fails",
|
||||
"type": "number"
|
||||
},
|
||||
"maxRetryDelay": {
|
||||
"description": "maximum delay when retrying write (milliseconds)",
|
||||
"type": "number"
|
||||
},
|
||||
"maxRetryTime": {
|
||||
"description": "max time (millis) that can be spent with retries",
|
||||
"type": "number"
|
||||
},
|
||||
"minRetryDelay": {
|
||||
"description": "minimum delay when retrying write (milliseconds)",
|
||||
"type": "number"
|
||||
},
|
||||
"randomRetry": {
|
||||
"description": "randomRetry indicates whether the next retry delay is deterministic (false) or random (true).\nThe deterministic delay starts with `minRetryDelay * exponentialBase` and it is multiplied\nby `exponentialBase` until it exceeds `maxRetryDelay`.\nWhen random is `true`, the next delay is computed as a random number between next retry attempt (upper)\nand the lower number in the deterministic sequence. `random(retryJitter)` is added to every returned value.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"retryJitter": {
|
||||
"description": "add `random(retryJitter)` milliseconds delay when retrying HTTP calls",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"batchSize",
|
||||
"exponentialBase",
|
||||
"flushInterval",
|
||||
"maxBatchBytes",
|
||||
"maxBufferLines",
|
||||
"maxRetries",
|
||||
"maxRetryDelay",
|
||||
"maxRetryTime",
|
||||
"minRetryDelay",
|
||||
"randomRetry",
|
||||
"retryJitter"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Configuration for application-level settings IE for running the bot instance\n\n* To load a JSON configuration **from the command line** use the `-c` cli argument EX: `node src/index.js -c /path/to/JSON/config.json`\n* To load a JSON configuration **using an environmental variable** use `OPERATOR_CONFIG` EX: `OPERATOR_CONFIG=/path/to/JSON/config.json`",
|
||||
|
||||
@@ -3945,6 +3945,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -3910,6 +3910,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -6467,7 +6467,7 @@
|
||||
"properties": {
|
||||
"allowDuplicate": {
|
||||
"default": false,
|
||||
"description": "Add Note even if a Note already exists for this Activity",
|
||||
"description": "Add Note even if a Note already exists for this Activity\n\nUSE `existingNoteCheck` INSTEAD",
|
||||
"examples": [
|
||||
false
|
||||
],
|
||||
@@ -6523,6 +6523,21 @@
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
"existingNoteCheck": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/UserNoteCriteria"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": true,
|
||||
"description": "Check if there is an existing Note matching some criteria before adding the Note.\n\nIf this check passes then the Note is added. The value may be a boolean or UserNoteCriteria.\n\nBoolean convenience:\n\n* If `true` or undefined then CM generates a UserNoteCriteria that passes only if there is NO existing note matching note criteria\n* If `false` then no check is performed and Note is always added",
|
||||
"examples": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"itemIs": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -6590,6 +6605,23 @@
|
||||
"pattern": "^\\s*(?<opStr>>|>=|<|<=)\\s*(?<value>\\d+)\\s*(?<percent>%?)\\s*(?<duration>in\\s+\\d+\\s*(days?|weeks?|months?|years?|hours?|minutes?|seconds?|milliseconds?))?\\s*(?<extra>asc.*|desc.*)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"description": "The content of the Note to search For.\n\n* Can be a single string or list of strings to search for. Each string will be searched for case-insensitive, as a subset of note content.\n* Can also be Regular Expression if wrapped in forward slashes IE '\\/test.*\\/i'"
|
||||
},
|
||||
"referencesCurrentActivity": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search": {
|
||||
"default": "current",
|
||||
"description": "How to test the Toolbox Notes or Mod Actions for this Author:\n\n### current\n\nOnly the most recent note is checked for criteria\n\n### total\n\n`count` comparison of mod actions/notes must be found within all history\n\n* EX `count: > 3` => Must have more than 3 notes of `type`, total\n* EX `count: <= 25%` => Must have 25% or less of notes of `type`, total\n* EX: `count: > 3 in 1 week` => Must have more than 3 notes within the last week\n\n### consecutive\n\nThe `count` **number** of mod actions/notes must be found in a row.\n\nYou may also specify the time-based order in which to search the notes by specifying `ascending (asc)` or `descending (desc)` in the `count` value. Default is `descending`\n\n* EX `count: >= 3` => Must have 3 or more notes of `type` consecutively, in descending order\n* EX `count: < 2` => Must have less than 2 notes of `type` consecutively, in descending order\n* EX `count: > 4 asc` => Must have greater than 4 notes of `type` consecutively, in ascending order",
|
||||
|
||||
@@ -123,7 +123,7 @@ import {
|
||||
SubmissionState,
|
||||
SubredditCriteria,
|
||||
toFullModLogCriteria,
|
||||
toFullModNoteCriteria,
|
||||
toFullModNoteCriteria, toFullUserNoteCriteria,
|
||||
TypedActivityState,
|
||||
TypedActivityStates,
|
||||
UserNoteCriteria
|
||||
@@ -3235,10 +3235,11 @@ export class SubredditResources {
|
||||
}
|
||||
break;
|
||||
case 'userNotes':
|
||||
const unCriterias = (authorOpts[k] as UserNoteCriteria[]).map(x => toFullUserNoteCriteria(x));
|
||||
const notes = await this.userNotes.getUserNotes(item.author);
|
||||
let foundNoteResult: string[] = [];
|
||||
const notePass = () => {
|
||||
for (const noteCriteria of authorOpts[k] as UserNoteCriteria[]) {
|
||||
for (const noteCriteria of unCriterias) {
|
||||
const {count = '>= 1', search = 'current', type} = noteCriteria;
|
||||
const {
|
||||
value,
|
||||
@@ -3247,26 +3248,14 @@ export class SubredditResources {
|
||||
duration,
|
||||
extra = ''
|
||||
} = parseGenericValueOrPercentComparison(count);
|
||||
const cutoffDate = duration === undefined ? undefined : dayjs().subtract(duration);
|
||||
const order = extra.includes('asc') ? 'ascending' : 'descending';
|
||||
switch (search) {
|
||||
case 'current':
|
||||
if (notes.length > 0) {
|
||||
const currentNoteType = notes[notes.length - 1].noteType;
|
||||
foundNoteResult.push(`Current => ${currentNoteType}`);
|
||||
if (currentNoteType === type) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
foundNoteResult.push('No notes present');
|
||||
}
|
||||
break;
|
||||
case 'consecutive':
|
||||
if (isPercent) {
|
||||
throw new SimpleError(`When comparing UserNotes with 'consecutive' search 'count' cannot be a percentage. Given: ${count}`);
|
||||
}
|
||||
|
||||
let orderedNotes = cutoffDate === undefined ? notes : notes.filter(x => x.time.isSameOrAfter(cutoffDate));
|
||||
let orderedNotes = [...notes];
|
||||
if (order === 'descending') {
|
||||
orderedNotes = [...notes];
|
||||
orderedNotes.reverse();
|
||||
@@ -3274,7 +3263,7 @@ export class SubredditResources {
|
||||
let currCount = 0;
|
||||
let maxCount = 0;
|
||||
for (const note of orderedNotes) {
|
||||
if (note.noteType === type) {
|
||||
if(note.matches(noteCriteria, item)) {
|
||||
currCount++;
|
||||
maxCount = Math.max(maxCount, currCount);
|
||||
} else {
|
||||
@@ -3286,8 +3275,10 @@ export class SubredditResources {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case 'current':
|
||||
case 'total':
|
||||
const filteredNotes = notes.filter(x => x.noteType === type && cutoffDate === undefined || (x.time.isSameOrAfter(cutoffDate)));
|
||||
const notesToUse = search === 'current' ? [notes[notes.length - 1]] : notes;
|
||||
const filteredNotes = notesToUse.filter(x => x.matches(noteCriteria, item));
|
||||
if (isPercent) {
|
||||
// avoid divide by zero
|
||||
const percent = notes.length === 0 ? 0 : filteredNotes.length / notes.length;
|
||||
@@ -3297,7 +3288,7 @@ export class SubredditResources {
|
||||
}
|
||||
} else {
|
||||
foundNoteResult.push(`${filteredNotes.length} are ${type}`);
|
||||
if (comparisonTextOp(notes.filter(x => x.noteType === type).length, operator, value)) {
|
||||
if (comparisonTextOp(filteredNotes.length, operator, value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import {Cache} from 'cache-manager';
|
||||
import {isScopeError} from "../Utils/Errors";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import {UserNoteType} from "../Common/Infrastructure/Atomic";
|
||||
import {FullUserNoteCriteria, UserNoteCriteria} from "../Common/Infrastructure/Filters/FilterCriteria";
|
||||
import {parseGenericValueOrPercentComparison} from "../Common/Infrastructure/Comparisons";
|
||||
import {SnoowrapActivity} from "../Common/Infrastructure/Reddit";
|
||||
|
||||
interface RawUserNotesPayload {
|
||||
ver: number,
|
||||
@@ -251,6 +254,44 @@ export class UserNote {
|
||||
|
||||
}
|
||||
|
||||
public matches(criteria: FullUserNoteCriteria, item?: SnoowrapActivity) {
|
||||
if (criteria.type !== undefined) {
|
||||
if(typeof this.noteType === 'string') {
|
||||
if(this.noteType.toLowerCase() !== criteria.type.toLowerCase().trim()) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (criteria.note !== undefined && !criteria.note.some(x => x.test(this.text ?? ''))) {
|
||||
return false;
|
||||
}
|
||||
if(criteria.referencesCurrentActivity !== undefined) {
|
||||
if(criteria.referencesCurrentActivity) {
|
||||
if(item === undefined) {
|
||||
return false;
|
||||
}
|
||||
if(this.link === null) {
|
||||
return false;
|
||||
}
|
||||
if(!this.link.includes(item.id)) {
|
||||
return false;
|
||||
}
|
||||
} else if(this.link !== null && item !== undefined && this.link.includes(item.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const {duration} = parseGenericValueOrPercentComparison(criteria.count ?? '>= 1');
|
||||
if (duration !== undefined) {
|
||||
const cutoffDate = dayjs().subtract(duration);
|
||||
if (this.time.isSameOrAfter(cutoffDate)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public toRaw(constants: UserNotesConstants): RawNote {
|
||||
let m = this.modIndex;
|
||||
if(m === undefined && this.moderator !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user