Refactor window criteria to actually work as described

This commit is contained in:
FoxxMD
2021-06-16 00:27:04 -04:00
parent 75889cc927
commit 7acd62d787
7 changed files with 83 additions and 42 deletions

View File

@@ -118,7 +118,6 @@ export class Check implements ICheck {
this.logger.info(`❌ => Item did not pass 'itemIs' test`);
return [false, allResults];
}
debugger;
let authorPass = null;
if (this.authorIs.include !== undefined && this.authorIs.include.length > 0) {
for (const auth of this.authorIs.include) {

View File

@@ -3,11 +3,16 @@
* @pattern ^(-?)P(?=\d|T\d)(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)([DW]))?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$
* */
export type ISO8601 = string;
export type ActivityWindowType = Duration | number | ActivityWindowCriteria;
export type Duration = ISO8601 | DurationObject;
export type ActivityWindowType = DurationVal | number | ActivityWindowCriteria;
export type DurationVal = ISO8601 | DurationObject;
/**
* If both properties are defined then the first criteria met will be used IE if # of activities = count before duration is reached then count will be used, or vice versa
* If both criteria are defined then whichever is met first will be used
*
* EX 100 count, 90 days
*
* * If 90 days of activities = 40 activities => returns 40 activities
* * If 100 activities is only 20 days => 100 activities
* @minProperties 1
* @additionalProperties false
* */
@@ -18,7 +23,7 @@ export interface ActivityWindowCriteria {
* */
count?: number,
/**
* An ISO 8601 duration or Day.js duration object.
* An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) or [Day.js duration object](https://day.js.org/docs/en/durations/creating).
*
* The duration will be subtracted from the time when the rule is run to create a time range like this:
*
@@ -27,7 +32,7 @@ export interface ActivityWindowCriteria {
* EX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM
* @examples ["PT1M", {"minutes": 15}]
* */
duration?: Duration
duration?: DurationVal
}
/**

View File

@@ -3,7 +3,7 @@
"definitions": {
"ActivityWindowCriteria": {
"additionalProperties": false,
"description": "If both properties are defined then the first criteria met will be used IE if # of activities = count before duration is reached then count will be used, or vice versa",
"description": "If both criteria are defined then whichever is met first will be used\n\nEX 100 count, 90 days\n\n* If 90 days of activities = 40 activities => returns 40 activities\n* If 100 activities is only 20 days => 100 activities",
"minProperties": 1,
"properties": {
"count": {
@@ -22,7 +22,7 @@
"type": "string"
}
],
"description": "An ISO 8601 duration or Day.js duration object.\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"description": "An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) or [Day.js duration object](https://day.js.org/docs/en/durations/creating).\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"examples": [
"PT1M",
{

View File

@@ -23,7 +23,7 @@
"definitions": {
"ActivityWindowCriteria": {
"additionalProperties": false,
"description": "If both properties are defined then the first criteria met will be used IE if # of activities = count before duration is reached then count will be used, or vice versa",
"description": "If both criteria are defined then whichever is met first will be used\n\nEX 100 count, 90 days\n\n* If 90 days of activities = 40 activities => returns 40 activities\n* If 100 activities is only 20 days => 100 activities",
"minProperties": 1,
"properties": {
"count": {
@@ -42,7 +42,7 @@
"type": "string"
}
],
"description": "An ISO 8601 duration or Day.js duration object.\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"description": "An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) or [Day.js duration object](https://day.js.org/docs/en/durations/creating).\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"examples": [
"PT1M",
{

View File

@@ -3,7 +3,7 @@
"definitions": {
"ActivityWindowCriteria": {
"additionalProperties": false,
"description": "If both properties are defined then the first criteria met will be used IE if # of activities = count before duration is reached then count will be used, or vice versa",
"description": "If both criteria are defined then whichever is met first will be used\n\nEX 100 count, 90 days\n\n* If 90 days of activities = 40 activities => returns 40 activities\n* If 100 activities is only 20 days => 100 activities",
"minProperties": 1,
"properties": {
"count": {
@@ -22,7 +22,7 @@
"type": "string"
}
],
"description": "An ISO 8601 duration or Day.js duration object.\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"description": "An [ISO 8601 duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) or [Day.js duration object](https://day.js.org/docs/en/durations/creating).\n\nThe duration will be subtracted from the time when the rule is run to create a time range like this:\n\nendTime = NOW <----> startTime = (NOW - duration)\n\nEX endTime = 3:00PM <----> startTime = (NOW - 15 minutes) = 2:45PM -- so look for activities between 2:45PM and 3:00PM",
"examples": [
"PT1M",
{

View File

@@ -5,8 +5,14 @@ import dayjs, {Dayjs} from "dayjs";
import Mustache from "mustache";
import he from "he";
import {AuthorCriteria, RuleResult, UserNoteCriteria} from "../Rule";
import {ActivityWindowType, CommentState, SubmissionState, TypedActivityStates} from "../Common/interfaces";
import {normalizeName, truncateStringToLength} from "../util";
import {
ActivityWindowType,
CommentState,
DurationVal,
SubmissionState,
TypedActivityStates
} from "../Common/interfaces";
import {isActivityWindowCriteria, normalizeName, truncateStringToLength} from "../util";
import UserNotes from "../Subreddit/UserNotes";
import {Logger} from "winston";
@@ -21,31 +27,47 @@ export interface AuthorActivitiesOptions {
export async function getAuthorActivities(user: RedditUser, options: AuthorTypedActivitiesOptions): Promise<Array<Submission | Comment>> {
const {chunkSize: cs = 100} = options;
const {chunkSize: cs = 100, window: optWindow} = options;
let window: number | Dayjs,
let satisfiedCount: number | undefined,
satisfiedEndtime: Dayjs | undefined,
chunkSize = Math.min(cs, 100);
if (typeof options.window !== 'number') {
let durVal: DurationVal | undefined;
let duration: Duration | undefined;
if(isActivityWindowCriteria(optWindow)) {
satisfiedCount = optWindow.count;
durVal = optWindow.duration;
} else if(typeof optWindow === 'number') {
satisfiedCount = optWindow;
} else {
durVal = optWindow as DurationVal;
}
// if count is less than max limit (100) go ahead and just get that many. may result in faster response time for low numbers
if(satisfiedCount !== undefined) {
chunkSize = Math.min(chunkSize, satisfiedCount);
}
if(durVal !== undefined) {
const endTime = dayjs();
let d;
if (dayjs.isDuration(options.window)) {
d = options.window;
} else {
if (!dayjs.isDuration(durVal)) {
// @ts-ignore
d = dayjs.duration(options.window);
duration = dayjs.duration(durVal);
}
if (!dayjs.isDuration(d)) {
if (!dayjs.isDuration(duration)) {
// TODO print object
throw new Error('window given was not a number, a valid ISO8601 duration, a Day.js duration, or well-formed Duration options');
}
window = endTime.subtract(d.asMilliseconds(), 'milliseconds');
} else {
window = options.window;
// use whichever is smaller so we only do one api request if window is smaller than default chunk size
chunkSize = Math.min(chunkSize, window);
satisfiedEndtime = endTime.subtract(duration.asMilliseconds(), 'milliseconds');
}
if(satisfiedCount === undefined && satisfiedEndtime === undefined) {
throw new Error('window value was not valid');
}
let items: Array<Submission | Comment> = [];
let lastItemDate;
//let count = 1;
let listing;
switch (options.type) {
@@ -63,33 +85,38 @@ export async function getAuthorActivities(user: RedditUser, options: AuthorTyped
let offset = chunkSize;
while (!hitEnd) {
if (typeof window === 'number') {
hitEnd = listing.length >= window;
} else {
const listSlice = listing.slice(offset - chunkSize);
const listSlice = listing.slice(offset - chunkSize)
if (satisfiedCount !== undefined && items.length + listSlice.length >= satisfiedCount) {
// satisfied count
items = items.concat(listSlice).slice(0, satisfiedCount);
break;
}
if(satisfiedEndtime !== undefined) {
const truncatedItems = listSlice.filter((x) => {
const utc = x.created_utc * 1000;
const itemDate = dayjs(utc);
// @ts-ignore
return window.isBefore(itemDate);
return satisfiedEndtime.isBefore(itemDate);
});
if (truncatedItems.length !== listSlice.length) {
hitEnd = true;
// satisfied duration
items = items.concat(truncatedItems);
break;
}
items = items.concat(truncatedItems);
}
if (!hitEnd) {
hitEnd = listing.isFinished;
}
// if we got this far neither count or time was satisfied so just add all items from listing and fetch more is possible
items = items.concat(listSlice);
hitEnd = listing.isFinished;
if (!hitEnd) {
offset += chunkSize;
listing = await listing.fetchMore({amount: chunkSize});
} else if (typeof window === 'number') {
items = listing.slice(0, window + 1);
}
}
// TODO truncate items to window size when duration
return Promise.resolve(items);
}

View File

@@ -11,6 +11,7 @@ import Submission from "snoowrap/dist/objects/Submission";
import {Comment} from "snoowrap";
import {inflateSync, deflateSync} from "zlib";
import pako from "pako";
import {ActivityWindowCriteria} from "./Common/interfaces";
dayjs.extend(utc);
dayjs.extend(dduration);
@@ -331,3 +332,12 @@ export const deflateUserNotes = (usersObject: object) => {
const blob = Buffer.from(binaryData).toString('base64');
return blob;
}
export const isActivityWindowCriteria = (val: any): val is ActivityWindowCriteria => {
if (val !== null && typeof val === 'object') {
return (val.count !== undefined && typeof val.count === 'number') ||
// close enough
val.duration !== undefined;
}
return false;
}