mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-01-14 07:57:57 -05:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a891e2d42b | ||
|
|
ef372e531e | ||
|
|
fde2836208 | ||
|
|
021dd5b0c5 | ||
|
|
5bd38d367a | ||
|
|
95c65304d4 | ||
|
|
d765a639dc | ||
|
|
70a04a0db6 | ||
|
|
75fcfece84 | ||
|
|
4b26b7d371 | ||
|
|
3d26fd2e3b | ||
|
|
74925fa8d8 | ||
|
|
d02d70ded3 | ||
|
|
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 |
@@ -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"
|
||||
|
||||
@@ -43,11 +43,7 @@ export class FlairAction extends Action {
|
||||
if (item instanceof Submission) {
|
||||
if(!this.dryRun) {
|
||||
if (this.flair_template_id) {
|
||||
// typings are wrong for this function, flair_template_id should be accepted
|
||||
// assignFlair uses /api/flair (mod endpoint)
|
||||
// selectFlair uses /api/selectflair (self endpoint for user to choose their own flair for submission)
|
||||
// @ts-ignore
|
||||
await item.assignFlair({flair_template_id: this.flair_template_id}).then(() => {});
|
||||
await item.selectFlair({flair_template_id: this.flair_template_id}).then(() => {});
|
||||
item.link_flair_template_id = this.flair_template_id;
|
||||
} else {
|
||||
await item.assignFlair({text: renderedText, cssClass: renderedCss}).then(() => {});
|
||||
|
||||
@@ -748,10 +748,6 @@ export interface RegExResult {
|
||||
named: NamedGroup
|
||||
}
|
||||
|
||||
export interface RegExResultWithTest extends RegExResult {
|
||||
test: RegExp
|
||||
}
|
||||
|
||||
export type StrongCache = {
|
||||
authorTTL: number | boolean,
|
||||
userNotesTTL: number | boolean,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
PASS, triggeredIndicator, windowConfigToWindowCriteria
|
||||
} from "../util";
|
||||
import {
|
||||
RegExResultWithTest,
|
||||
RuleResult,
|
||||
} from "../Common/interfaces";
|
||||
import dayjs from 'dayjs';
|
||||
@@ -15,11 +14,10 @@ import {SimpleError} from "../Utils/Errors";
|
||||
import {JoinOperands} from "../Common/Infrastructure/Atomic";
|
||||
import {ActivityWindowConfig} from "../Common/Infrastructure/ActivityWindow";
|
||||
import {
|
||||
comparisonTextOp, GenericComparison,
|
||||
comparisonTextOp,
|
||||
parseGenericValueComparison,
|
||||
parseGenericValueOrPercentComparison
|
||||
} from "../Common/Infrastructure/Comparisons";
|
||||
import {SnoowrapActivity} from "../Common/Infrastructure/Reddit";
|
||||
|
||||
export interface RegexCriteria {
|
||||
/**
|
||||
@@ -29,23 +27,13 @@ export interface RegexCriteria {
|
||||
* */
|
||||
name?: string
|
||||
/**
|
||||
* A valid Regular Expression, or list of expressions, to test content against
|
||||
* A valid Regular Expression to test content against
|
||||
*
|
||||
* If no flags are specified then the **global** flag is used by default
|
||||
*
|
||||
* @examples ["/reddit|FoxxMD/ig"]
|
||||
* */
|
||||
regex: string | string[],
|
||||
|
||||
/**
|
||||
* Determines if ALL regexes listed are run or if regexes are only run until one is matched.
|
||||
*
|
||||
* * `true` => all regexes are always run
|
||||
* * `false` => regexes are run until one matches
|
||||
*
|
||||
* @default false
|
||||
* */
|
||||
exhaustive?: boolean
|
||||
regex: string,
|
||||
|
||||
/**
|
||||
* Which content from an Activity to test the regex against
|
||||
@@ -169,7 +157,6 @@ export class RegexRule extends Rule {
|
||||
const {
|
||||
name = (index + 1),
|
||||
regex,
|
||||
exhaustive = false,
|
||||
testOn: testOnVals = ['title', 'body'],
|
||||
lookAt = 'all',
|
||||
matchThreshold = '> 0',
|
||||
@@ -187,7 +174,13 @@ export class RegexRule extends Rule {
|
||||
return acc.concat(curr);
|
||||
}, []);
|
||||
|
||||
const regexTests: RegExp[] = await this.convertToRegexArray(name, regex);
|
||||
// check regex
|
||||
const regexContent = await this.resources.getContent(regex);
|
||||
const reg = parseStringToRegex(regexContent, 'g');
|
||||
if(reg === undefined) {
|
||||
throw new SimpleError(`Value given for regex on Criteria ${name} was not valid: ${regex}`);
|
||||
}
|
||||
// ok cool its a valid regex
|
||||
|
||||
const matchComparison = parseGenericValueComparison(matchThreshold);
|
||||
const activityMatchComparison = activityMatchThreshold === null ? undefined : parseGenericValueOrPercentComparison(activityMatchThreshold);
|
||||
@@ -205,13 +198,12 @@ export class RegexRule extends Rule {
|
||||
|
||||
// first lets see if the activity we are checking satisfies thresholds
|
||||
// since we may be able to avoid api calls to get history
|
||||
let actMatches = getMatchesFromActivity(item, testOn, regexTests, exhaustive);
|
||||
const actMatchSummary = regexResultsSummary(actMatches);
|
||||
matches = matches.concat(actMatchSummary.matches).slice(0, 100);
|
||||
matchCount += actMatchSummary.matches.length;
|
||||
let actMatches = this.getMatchesFromActivity(item, testOn, reg);
|
||||
matches = matches.concat(actMatches).slice(0, 100);
|
||||
matchCount += actMatches.length;
|
||||
|
||||
activitiesTested++;
|
||||
const singleMatched = comparisonTextOp(actMatchSummary.matches.length, matchComparison.operator, matchComparison.value);
|
||||
const singleMatched = comparisonTextOp(actMatches.length, matchComparison.operator, matchComparison.value);
|
||||
if (singleMatched) {
|
||||
activitiesMatchedCount++;
|
||||
}
|
||||
@@ -241,7 +233,7 @@ export class RegexRule extends Rule {
|
||||
}
|
||||
|
||||
history = await this.resources.getAuthorActivities(item.author, strongWindow);
|
||||
// remove current activity if it exists in history so we don't count it twice
|
||||
// remove current activity it exists in history so we don't count it twice
|
||||
history = history.filter(x => x.id !== item.id);
|
||||
const historyLength = history.length;
|
||||
|
||||
@@ -260,12 +252,10 @@ export class RegexRule extends Rule {
|
||||
|
||||
for (const h of history) {
|
||||
activitiesTested++;
|
||||
const aMatches = getMatchesFromActivity(h, testOn, regexTests, exhaustive);
|
||||
actMatches = actMatches.concat(aMatches);
|
||||
const actHistoryMatchSummary = regexResultsSummary(aMatches);
|
||||
matches = matches.concat(actHistoryMatchSummary.matches).slice(0, 100);
|
||||
matchCount += actHistoryMatchSummary.matches.length;
|
||||
const matched = comparisonTextOp(actHistoryMatchSummary.matches.length, matchComparison.operator, matchComparison.value);
|
||||
const aMatches = this.getMatchesFromActivity(h, testOn, reg);
|
||||
matches = matches.concat(aMatches).slice(0, 100);
|
||||
matchCount += aMatches.length;
|
||||
const matched = comparisonTextOp(aMatches.length, matchComparison.operator, matchComparison.value);
|
||||
if (matched) {
|
||||
activitiesMatchedCount++;
|
||||
}
|
||||
@@ -292,19 +282,10 @@ export class RegexRule extends Rule {
|
||||
humanWindow = '1 Item';
|
||||
}
|
||||
|
||||
// to provide at least one useful regex for this criteria
|
||||
// use the first regex found by default
|
||||
let relevantRegex: string = regexTests[0].toString();
|
||||
// but if more than one regex was listed AND we did have matches
|
||||
// then use the first regex that actually got a match
|
||||
if(regexTests.length > 0 && actMatches.length > 0) {
|
||||
relevantRegex = actMatches[0].test.toString();
|
||||
}
|
||||
|
||||
const critResults = {
|
||||
criteria: {
|
||||
name,
|
||||
regex: relevantRegex,
|
||||
regex: regex !== regexContent ? `${regex} from ${regexContent}` : regex,
|
||||
testOn,
|
||||
matchThreshold,
|
||||
activityMatchThreshold,
|
||||
@@ -371,115 +352,42 @@ export class RegexRule extends Rule {
|
||||
return Promise.resolve([criteriaMet, this.getResult(criteriaMet, {result, data: {results: criteriaResults, matchSample }})]);
|
||||
}
|
||||
|
||||
protected async convertToRegexArray(name: string | number, value: string | string[]): Promise<RegExp[]> {
|
||||
const regexTests: RegExp[] = [];
|
||||
const regexStringVals = typeof value === 'string' ? [value] : value;
|
||||
for(const r of regexStringVals) {
|
||||
// check regex
|
||||
const regexContent = await this.resources.getContent(r);
|
||||
const reg = parseStringToRegex(regexContent, 'ig');
|
||||
if (reg === undefined) {
|
||||
throw new SimpleError(`Value given for regex on Criteria ${name} was not valid: ${value}`);
|
||||
}
|
||||
// ok cool its a valid regex
|
||||
regexTests.push(reg);
|
||||
}
|
||||
return regexTests;
|
||||
}
|
||||
}
|
||||
|
||||
export const getMatchResultsFromContent = (contents: string[], reg: RegExp): RegExResultWithTest[] => {
|
||||
let m: RegExResultWithTest[] = [];
|
||||
for (const c of contents) {
|
||||
const results = parseRegex(reg, c);
|
||||
if(results !== undefined) {
|
||||
for(const r of results) {
|
||||
m.push({...r, test: reg});
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
export const regexResultsSummary = (results: RegExResultWithTest[]) => {
|
||||
const matchResults: ActivityMatchResults = {
|
||||
matches: [],
|
||||
matchesByTest: {},
|
||||
groups: {}
|
||||
}
|
||||
|
||||
for (const r of results) {
|
||||
if (matchResults.matchesByTest[r.test.toString()] === undefined) {
|
||||
matchResults.matchesByTest[r.test.toString()] = [];
|
||||
}
|
||||
matchResults.matchesByTest[r.test.toString()].push(r.match);
|
||||
matchResults.matches.push(r.match);
|
||||
if (r.named !== undefined) {
|
||||
Object.entries(r.named).forEach(([key, val]) => {
|
||||
if (matchResults.groups[key] === undefined) {
|
||||
matchResults.groups[key] = [];
|
||||
protected getMatchesFromActivity(a: (Submission | Comment), testOn: string[], reg: RegExp): string[] {
|
||||
let m: string[] = [];
|
||||
// determine what content we are testing
|
||||
let contents: string[] = [];
|
||||
if (asSubmission(a)) {
|
||||
for (const l of testOn) {
|
||||
switch (l) {
|
||||
case 'title':
|
||||
contents.push(a.title);
|
||||
break;
|
||||
case 'body':
|
||||
if (a.is_self) {
|
||||
contents.push(a.selftext);
|
||||
}
|
||||
break;
|
||||
case 'url':
|
||||
if (isExternalUrlSubmission(a)) {
|
||||
contents.push(a.url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
matchResults.groups[key].push(val);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
contents.push(a.body)
|
||||
}
|
||||
}
|
||||
return matchResults;
|
||||
}
|
||||
|
||||
export const getMatchesFromActivity = (a: (Submission | Comment), testOn: string[], regexes: RegExp[], exhaustive: boolean): RegExResultWithTest[] => {
|
||||
// determine what content we are testing
|
||||
let contents: string[] = getMatchableContent(a, testOn);
|
||||
let results: RegExResultWithTest[] = [];
|
||||
|
||||
for (const reg of regexes) {
|
||||
const res = getMatchResultsFromContent(contents, reg);
|
||||
if(res.length > 0) {
|
||||
results = results.concat(res);
|
||||
// only continue testing if the user wants to exhaustively check all regexes (to get more matches?)
|
||||
if(!exhaustive) {
|
||||
return results;
|
||||
for (const c of contents) {
|
||||
const results = parseRegex(reg, c);
|
||||
if(results !== undefined) {
|
||||
for(const r of results) {
|
||||
m.push(r.match);
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const getMatchableContent = (a: SnoowrapActivity, testOn: string[]) => {
|
||||
let contents: string[] = [];
|
||||
if (asSubmission(a)) {
|
||||
for (const l of testOn) {
|
||||
switch (l) {
|
||||
case 'title':
|
||||
contents.push(a.title);
|
||||
break;
|
||||
case 'body':
|
||||
if (a.is_self) {
|
||||
contents.push(a.selftext);
|
||||
}
|
||||
break;
|
||||
case 'url':
|
||||
if (isExternalUrlSubmission(a)) {
|
||||
contents.push(a.url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
contents.push(a.body)
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
|
||||
interface RegexMatchComparisonOptions {
|
||||
matchComparison: GenericComparison
|
||||
activityMatchComparison?: GenericComparison
|
||||
totalMatchComparison?: GenericComparison
|
||||
}
|
||||
|
||||
interface ActivityMatchResults {
|
||||
matches: string[]
|
||||
matchesByTest: Record<string, string[]>
|
||||
groups: Record<string, string[]>
|
||||
}
|
||||
|
||||
interface RegexConfig {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {Logger} from "winston";
|
||||
import {WebSetting} from "../../Common/WebEntities/WebSetting";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import {createCacheManager} from "../../Common/Cache";
|
||||
import {MysqlDriver} from "typeorm/driver/mysql/MysqlDriver";
|
||||
|
||||
export interface CacheManagerStoreOptions {
|
||||
prefix?: string
|
||||
@@ -103,7 +104,12 @@ export class DatabaseStorageProvider extends StorageProvider {
|
||||
}
|
||||
|
||||
createSessionStore(options?: TypeormStoreOptions): Store {
|
||||
return new TypeormStore(options).connect(this.clientSessionRepo)
|
||||
// https://github.com/freshgiammi-lab/connect-typeorm#implement-the-session-entity
|
||||
// https://github.com/freshgiammi-lab/connect-typeorm/issues/8
|
||||
// usage of LIMIT in subquery is not supported by mariadb/mysql
|
||||
// limitSubquery: false -- turns off LIMIT usage
|
||||
const realOptions = this.database.driver instanceof MysqlDriver ? {...options, limitSubquery: false} : options;
|
||||
return new TypeormStore(realOptions).connect(this.clientSessionRepo)
|
||||
}
|
||||
|
||||
async getSessionSecret(): Promise<string | undefined> {
|
||||
|
||||
Reference in New Issue
Block a user