mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-01-14 07:57:57 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39daa11f2d | ||
|
|
93de38a845 | ||
|
|
43caaca1f2 | ||
|
|
7bcc0195fe | ||
|
|
dac6541e28 | ||
|
|
2504a34a34 | ||
|
|
e19639ad0d | ||
|
|
b8084e02b5 | ||
|
|
97906281e6 | ||
|
|
2cea119657 | ||
|
|
6f16d289dd | ||
|
|
a96575c6b3 | ||
|
|
0a82e83352 | ||
|
|
d5e1cdec61 | ||
|
|
ef40c25b09 | ||
|
|
6370a2976a | ||
|
|
d8180299ea | ||
|
|
ac409dce3d | ||
|
|
56c007c20d |
16
Dockerfile
16
Dockerfile
@@ -1,14 +1,10 @@
|
||||
FROM node:16-alpine3.12
|
||||
FROM node:16-alpine3.14
|
||||
|
||||
ENV TZ=Etc/GMT
|
||||
|
||||
RUN apk update
|
||||
|
||||
# required dependencies in order to compile linux-musl (node-canvas) on alpine
|
||||
# https://github.com/node-gfx/node-canvas-prebuilt/issues/77#issuecomment-884365161
|
||||
RUN apk add --no-cache build-base g++ cairo-dev jpeg-dev pango-dev giflib-dev
|
||||
# required dependencies in order to compile linux-musl (node-canvas) on alpine
|
||||
RUN apk add --update --repository http://dl-3.alpinelinux.org/alpine/edge/testing libmount ttf-dejavu ttf-droid ttf-freefont ttf-liberation ttf-ubuntu-font-family fontconfig vips
|
||||
# vips required to run sharp library for image comparison
|
||||
RUN echo "http://dl-4.alpinelinux.org/alpine/v3.14/community" >> /etc/apk/repositories \
|
||||
&& apk --update add vips
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
@@ -17,9 +13,7 @@ WORKDIR /usr/app
|
||||
COPY package*.json ./
|
||||
COPY tsconfig.json .
|
||||
|
||||
# no prebuild support for node-canvas on alpine so need to compile
|
||||
# https://github.com/Automattic/node-canvas#compiling
|
||||
RUN npm install --build-from-source
|
||||
RUN npm install
|
||||
|
||||
ADD . /usr/app
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -656,10 +656,7 @@ export const buildOperatorConfigWithDefaults = (data: OperatorJsonConfig): Opera
|
||||
...cacheTTLDefaults,
|
||||
actionedEventsDefault: opActionedEventsDefault,
|
||||
actionedEventsMax: opActionedEventsMax,
|
||||
provider: {
|
||||
store: 'memory',
|
||||
...cacheOptDefaults
|
||||
}
|
||||
provider: {...defaultProvider}
|
||||
};
|
||||
} else {
|
||||
const {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -12,11 +12,11 @@ export interface AuthorRuleConfig {
|
||||
/**
|
||||
* Will "pass" if any set of AuthorCriteria passes
|
||||
* */
|
||||
include: AuthorCriteria[];
|
||||
include?: AuthorCriteria[];
|
||||
/**
|
||||
* Only runs if include is not present. Will "pass" if any of set of the AuthorCriteria does not pass
|
||||
* */
|
||||
exclude: AuthorCriteria[];
|
||||
exclude?: AuthorCriteria[];
|
||||
}
|
||||
|
||||
export interface AuthorRuleOptions extends AuthorRuleConfig, RuleOptions {
|
||||
@@ -34,8 +34,13 @@ export class AuthorRule extends Rule {
|
||||
constructor(options: AuthorRuleOptions) {
|
||||
super(options);
|
||||
|
||||
this.include = options.include.map(x => new Author(x));
|
||||
this.exclude = options.exclude.map(x => new Author(x));
|
||||
const {
|
||||
include,
|
||||
exclude,
|
||||
} = options;
|
||||
|
||||
this.include = include !== undefined ? include.map(x => new Author(x)) : [];
|
||||
this.exclude = exclude !== undefined ? exclude.map(x => new Author(x)) : [];
|
||||
|
||||
if(this.include.length === 0 && this.exclude.length === 0) {
|
||||
throw new Error('At least one of the properties [include,exclude] on Author Rule must not be empty');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
@@ -621,8 +671,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"exclude",
|
||||
"include",
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
|
||||
@@ -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": [
|
||||
@@ -567,8 +617,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"exclude",
|
||||
"include",
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
|
||||
@@ -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": [
|
||||
@@ -544,8 +594,6 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"exclude",
|
||||
"include",
|
||||
"kind"
|
||||
],
|
||||
"type": "object"
|
||||
|
||||
@@ -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,31 @@ 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;
|
||||
case 'op':
|
||||
if(item instanceof Submission) {
|
||||
log.warn(`On a Submission the 'op' property will always be true. Did you mean to use this on a comment instead?`);
|
||||
break;
|
||||
}
|
||||
// @ts-ignore
|
||||
if (item.is_submitter !== 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 +920,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;
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -4,7 +4,7 @@ a {
|
||||
|
||||
.loading {
|
||||
height: 35px;
|
||||
fill: black;
|
||||
fill: white;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,6 @@ a {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.dark .loading {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.sub {
|
||||
display: none;
|
||||
}
|
||||
@@ -91,3 +87,19 @@ a {
|
||||
pointer-events: initial;
|
||||
text-decoration: initial;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: rgb(97, 175, 239)
|
||||
}
|
||||
.red {
|
||||
color: rgb(255 123 133);
|
||||
}
|
||||
.green {
|
||||
color: rgb(170 255 109);
|
||||
}
|
||||
.purple {
|
||||
color: rgb(224 124 253);
|
||||
}
|
||||
.yellow {
|
||||
color: rgb(253 198 94);
|
||||
}
|
||||
|
||||
10
src/Web/assets/public/questionsymbol.svg
Normal file
10
src/Web/assets/public/questionsymbol.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<symbol id="q" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
1
src/Web/assets/public/tailwind.min.css
vendored
Normal file
1
src/Web/assets/public/tailwind.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,12 +1,11 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: 'CM OAuth Helper'}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title', {title: ' OAuth Helper'}) %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-600">
|
||||
<div class="p-6 md:px-10 md:py-6">
|
||||
<div class="text-xl mb-4">Congrats! You did the thing.</div>
|
||||
<div class="space-y-3">
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css"
|
||||
integrity="sha512-wl80ucxCRpLkfaCnbM88y4AxnutbGk327762eM9E/rRTvY/ZGAHWMZrYUq66VQBYMIYDFpDdJAOGSLyIPHZ2IQ=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind-dark.min.css"
|
||||
integrity="sha512-WvyKyiVHgInX5UQt67447ExtRRZG/8GUijaq1MpqTNYp8wY4/EJOG5bI80sRp/5crDy4Z6bBUydZI2OFV3Vbtg=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="/public/tailwind.min.css"/>
|
||||
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"></script>
|
||||
<link rel="stylesheet" href="/public/themeToggle.css">
|
||||
<link rel="stylesheet" href="/public/app.css">
|
||||
@@ -15,13 +10,12 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<!--icons from https://heroicons.com -->
|
||||
</head>
|
||||
<body style="user-select: none;" class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body style="user-select: none;" class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen bg-gray-800">
|
||||
<%- include('partials/title') %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="dark:text-white mb-3 pl-2">
|
||||
<div class="my-3 pl-2">
|
||||
Schema <a href="/config?schema=subreddit" id="subredditSchemaType">Subreddit</a> / <a href="/config?schema=operator" id="operatorSchemaType">Operator</a> |
|
||||
<span class="has-tooltip">
|
||||
<span style="z-index:999; margin-top: 30px;" class='tooltip rounded shadow-lg p-3 bg-gray-100 text-black space-y-2'>
|
||||
@@ -63,35 +57,6 @@
|
||||
</div>
|
||||
<%- include('partials/footer') %>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
if (e.target.id === 'dark') {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.classList.remove('font-bold', 'no-underline', 'pointer-events-none');
|
||||
});
|
||||
e.target.classList.add('font-bold', 'no-underline', 'pointer-events-none');
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelector("#themeToggle").checked = localStorage.getItem('ms-dark') !== 'no';
|
||||
document.querySelector("#themeToggle").onchange = (e) => {
|
||||
if (e.target.checked === true) {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/monaco/dev/vs/loader.js"></script>
|
||||
<script>
|
||||
require.config({ paths: { vs: 'monaco/dev/vs' } });
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: 'CM'}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title', {title: 'Error'}) %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-600">
|
||||
<div class="p-6 md:px-10 md:py-6">
|
||||
<div class="text-xl mb-4">Oops 😬</div>
|
||||
<div class="space-y-3">
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css"
|
||||
integrity="sha512-wl80ucxCRpLkfaCnbM88y4AxnutbGk327762eM9E/rRTvY/ZGAHWMZrYUq66VQBYMIYDFpDdJAOGSLyIPHZ2IQ=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind-dark.min.css"
|
||||
integrity="sha512-WvyKyiVHgInX5UQt67447ExtRRZG/8GUijaq1MpqTNYp8wY4/EJOG5bI80sRp/5crDy4Z6bBUydZI2OFV3Vbtg=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="/public/tailwind.min.css"/>
|
||||
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"></script>
|
||||
<link rel="stylesheet" href="/public/themeToggle.css">
|
||||
<link rel="stylesheet" href="/public/app.css">
|
||||
@@ -20,19 +15,19 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title') %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white px-3 py-6 space-y-3">
|
||||
<div class="px-3 py-6 space-y-3">
|
||||
<% if(data.length === 0) { %>
|
||||
No events have been actioned yet!
|
||||
<% } %>
|
||||
<% data.forEach(function (eRes){ %>
|
||||
<div class="shadow-lg">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 dark:bg-gray-700 dark:text-white">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="peek"><%- eRes.activity.peek %></span><a target="_blank" href="https://reddit.com<%= eRes.activity.link%>">(Link)</a>
|
||||
@@ -42,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 pl-6 pt-3 space-y-2">
|
||||
<div class="p-4 pl-6 pt-3 space-y-2 bg-gray-500">
|
||||
<div><span class="font-semibold">Check:</span> <%= eRes.check %><span class="px-3">➔</span><%= eRes.ruleSummary %></div>
|
||||
<div>
|
||||
<span class="font-semibold">Rules:</span>
|
||||
@@ -67,34 +62,5 @@
|
||||
</div>
|
||||
<%- include('partials/footer') %>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
if (e.target.id === 'dark') {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.classList.remove('font-bold', 'no-underline', 'pointer-events-none');
|
||||
});
|
||||
e.target.classList.add('font-bold', 'no-underline', 'pointer-events-none');
|
||||
})
|
||||
})
|
||||
|
||||
document.querySelector("#themeToggle").checked = localStorage.getItem('ms-dark') !== 'no';
|
||||
document.querySelector("#themeToggle").onchange = (e) => {
|
||||
if (e.target.checked === true) {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: 'CM OAuth Helper'}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title', {title: ' OAuth Helper'}) %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-600">
|
||||
<div class="p-6 md:px-10 md:py-6">
|
||||
<div class="text-xl mb-4">Hi! Looks like you're setting up your bot. To get running:</div>
|
||||
<div class="text-lg text-semibold my-3">1. Set your redirect URL</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: 'CM OAuth Helper'}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white">
|
||||
<div class="min-w-screen min-h-screen font-sans">
|
||||
<%- include('partials/title', {title: ' OAuth Helper'}) %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-600">
|
||||
<div class="p-6 md:px-10 md:py-6">
|
||||
<div class="text-xl mb-4">Hi! Looks like you're accepting an invite to authorize an account to run on this ContextMod instance:</div>
|
||||
<div class="text-lg text-semibold my-3">1. Review permissions</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: 'Access Denied'}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title', {title: ''}) %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-600">
|
||||
<div class="p-6 md:px-10 md:py-6">
|
||||
<div class="text-xl mb-4">Sorry!</div>
|
||||
<div class="space-y-3">
|
||||
@@ -28,6 +27,5 @@
|
||||
</div>
|
||||
<%- include('partials/footer') %>
|
||||
</div>
|
||||
<%- include('partials/themeJs') %>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: undefined}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/header') %>
|
||||
<%- include('partials/botsTab') %>
|
||||
<div class="container mx-auto">
|
||||
<%- include('partials/subredditsTab') %>
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="bg-gray-700">
|
||||
<div class="pb-6 md:px-7">
|
||||
<div class="sub active" data-subreddit="All" data-bot="All">
|
||||
Instance is currently <b>OFFLINE</b>
|
||||
@@ -31,7 +30,6 @@
|
||||
</div>
|
||||
<%- include('partials/footer') %>
|
||||
<%- include('partials/instanceTabJs') %>
|
||||
<%- include('partials/themeJs') %>
|
||||
<%- include('partials/logSettingsJs') %>
|
||||
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="space-x-4 py-1 md:px-10 leading-6 font-semibold bg-gray-500 dark:bg-gray-700 text-white">
|
||||
<div class="space-x-4 py-1 md:px-10 leading-6 font-semibold bg-gray-700">
|
||||
<div class="container mx-auto">
|
||||
<% if(locals.bots !== undefined) { %>
|
||||
<ul id="botTabs" class="inline-flex flex-wrap">
|
||||
<% bots.forEach(function (data){ %>
|
||||
<li class="my-3 px-3 dark:text-white">
|
||||
<li class="my-3 px-3">
|
||||
<span data-bot="<%= data.system.name %>" class="rounded-md py-2 px-3 tabSelectWrapper">
|
||||
<a class="tabSelect font-normal pointer hover:font-bold"
|
||||
data-bot="<%= data.system.name %>">
|
||||
@@ -16,7 +16,7 @@
|
||||
</li>
|
||||
<% }) %>
|
||||
<% if(locals.isOperator === true && locals.instanceId !== undefined) { %>
|
||||
<li class="my-3 px-3 dark:text-white">
|
||||
<li class="my-3 px-3">
|
||||
<span class="rounded-md py-2 px-3 border">
|
||||
<a class="font-normal pointer hover:font-bold" href="/auth/helper">
|
||||
Add Bot +
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="py-3 flex items-center justify-around font-semibold text-white">
|
||||
<div class="py-3 flex items-center justify-around font-semibold">
|
||||
<div>
|
||||
<a href="https://github.com/FoxxMD/context-mod">ContextMod Web</a> created by /u/FoxxMD
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css"
|
||||
integrity="sha512-wl80ucxCRpLkfaCnbM88y4AxnutbGk327762eM9E/rRTvY/ZGAHWMZrYUq66VQBYMIYDFpDdJAOGSLyIPHZ2IQ=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind-dark.min.css"
|
||||
integrity="sha512-WvyKyiVHgInX5UQt67447ExtRRZG/8GUijaq1MpqTNYp8wY4/EJOG5bI80sRp/5crDy4Z6bBUydZI2OFV3Vbtg=="
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="/public/tailwind.min.css"/>
|
||||
<script src="https://code.iconify.design/1/1.0.4/iconify.min.js"></script>
|
||||
<link rel="stylesheet" href="/public/themeToggle.css">
|
||||
<link rel="stylesheet" href="/public/app.css">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="space-x-4 p-6 md:px-10 md:py-6 leading-6 font-semibold bg-gray-800 text-white">
|
||||
<div class="space-x-4 p-6 md:px-10 md:py-6 leading-6 font-semibold bg-gray-500 text-white">
|
||||
<div class="container mx-auto">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center flex-grow pr-4">
|
||||
<% if(locals.instances !== undefined) { %>
|
||||
<ul class="inline-flex flex-wrap">
|
||||
<% instances.forEach(function (data) { %>
|
||||
<li class="my-3 px-3 dark:text-white">
|
||||
<li class="my-3 px-3">
|
||||
<span data-instance="<%= data.friendly %>" class="has-tooltip instanceSelectWrapper rounded-md py-3 px-3">
|
||||
<span class='tooltip rounded shadow-lg p-1 bg-gray-100 text-black' style="margin-top:3em">
|
||||
<div class="stats">
|
||||
@@ -34,20 +34,6 @@
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="flex items-center flex-end text-sm">
|
||||
<span class="inline-block mr-4">
|
||||
<label style="font-size:2.5px;">
|
||||
<input class='toggle-checkbox' type='checkbox' id="themeToggle" checked></input>
|
||||
<div class='toggle-slot'>
|
||||
<div class='sun-icon-wrapper'>
|
||||
<div class="iconify sun-icon" data-icon="feather-sun" data-inline="false"></div>
|
||||
</div>
|
||||
<div class='toggle-button'></div>
|
||||
<div class='moon-icon-wrapper'>
|
||||
<div class="iconify moon-icon" data-icon="feather-moon" data-inline="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
<a href="logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="space-x-4 pt-2 md:px-5 leading-6 font-semibold bg-gray-white dark:bg-gray-500 text-white">
|
||||
<div class="space-x-4 pt-2 md:px-5 leading-6 font-semibold bg-gray-800">
|
||||
<div class="container mx-auto">
|
||||
<% if(locals.bots !== undefined) { %>
|
||||
<% bots.forEach(function (botData){ %>
|
||||
<ul data-bot="<%= botData.system.name %>" class="inline-flex flex-wrap subreddit nestedTabs">
|
||||
<% botData.subreddits.forEach(function (data){ %>
|
||||
<li class="my-3 pr-3 dark:text-white">
|
||||
<li class="my-3 pr-3">
|
||||
<span data-subreddit="<%= data.name %>" class="rounded-md py-2 px-3 tabSelectWrapper">
|
||||
<a class="tabSelect font-normal pointer hover:font-bold"
|
||||
data-subreddit="<%= data.name %>">
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<script>
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
if (e.target.id === 'dark') {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
document.querySelectorAll('.theme').forEach(el => {
|
||||
el.classList.remove('font-bold', 'no-underline', 'pointer-events-none');
|
||||
});
|
||||
e.target.classList.add('font-bold', 'no-underline', 'pointer-events-none');
|
||||
})
|
||||
});
|
||||
|
||||
const themeToggle = document.querySelector("#themeToggle");
|
||||
if(themeToggle !== null) {
|
||||
themeToggle.checked = localStorage.getItem('ms-dark') !== 'no';
|
||||
themeToggle.onchange = (e) => {
|
||||
if (e.target.checked === true) {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem('ms-dark', 'yes');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem('ms-dark', 'no');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="space-x-4 p-6 md:px-10 md:py-6 leading-6 font-semibold bg-gray-800 text-white">
|
||||
<div class="space-x-4 p-6 md:px-10 md:py-6 leading-6 font-semibold bg-gray-500">
|
||||
<div class="container mx-auto">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center flex-grow pr-4">
|
||||
@@ -7,20 +7,7 @@
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="flex items-center flex-end text-sm">
|
||||
<span class="inline-block mr-4">
|
||||
<label style="font-size:2.5px;">
|
||||
<input class='toggle-checkbox' type='checkbox' id="themeToggle" checked></input>
|
||||
<div class='toggle-slot'>
|
||||
<div class='sun-icon-wrapper'>
|
||||
<div class="iconify sun-icon" data-icon="feather-sun" data-inline="false"></div>
|
||||
</div>
|
||||
<div class='toggle-button'></div>
|
||||
<div class='moon-icon-wrapper'>
|
||||
<div class="iconify moon-icon" data-icon="feather-moon" data-inline="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
<a href="logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<html>
|
||||
<html lang="en">
|
||||
<%- include('partials/head', {title: undefined}) %>
|
||||
<body class="">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen bg-gray-100 bg-gray-100 dark:bg-gray-800 font-sans">
|
||||
<body class="bg-gray-900 text-white">
|
||||
<div class="min-w-screen min-h-screen font-sans">
|
||||
<%- include('partials/header') %>
|
||||
<%- include('partials/botsTab') %>
|
||||
<%- include('partials/subredditsTab') %>
|
||||
<div class="container mx-auto">
|
||||
<%- include('partials/subredditsTab') %>
|
||||
<div class="grid">
|
||||
<div class="bg-white dark:bg-gray-500 dark:text-white">
|
||||
<div class="">
|
||||
<div class="pb-6 md:px-7">
|
||||
<% bots.forEach(function (bot){ %>
|
||||
<% bot.subreddits.forEach(function (data){ %>
|
||||
<div class="sub <%= bot.system.running ? '' : 'offline' %>" data-subreddit="<%= data.name %>" data-bot="<%= bot.system.name %>">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3 gap-5">
|
||||
<div class="bg-white shadow-md rounded my-3 dark:bg-gray-500 dark:text-white">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 dark:bg-gray-700 dark:text-white">
|
||||
<div class="bg-white shadow-md rounded my-3 bg-gray-600 ">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 bg-gray-700 ">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4>Overview</h4>
|
||||
<% if (data.name === 'All') { %>
|
||||
@@ -63,10 +62,7 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -99,10 +95,7 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -136,10 +129,7 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -209,11 +199,8 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -235,8 +222,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<% if (data.name === 'All') { %>
|
||||
<div class="bg-white shadow-md rounded my-3 dark:bg-gray-500 dark:text-white">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 dark:bg-gray-700 dark:text-white">
|
||||
<div class="bg-white shadow-md rounded my-3 bg-gray-600 ">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 bg-gray-700 ">
|
||||
<h4>API</h4>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
@@ -250,9 +237,40 @@
|
||||
<label>Api Usage</label>
|
||||
<span><span id="apiLimit"><%= data.apiLimit %></span>/600 (~<span
|
||||
id="apiAvg"><%= data.apiAvg %></span>req/s)</span>
|
||||
<label>Depleted</label>
|
||||
<label>
|
||||
<span class="has-tooltip">
|
||||
<span style="margin-top:55px" class='tooltip rounded shadow-lg p-1 bg-gray-100 text-black space-y-3 p-2 text-left'>
|
||||
<p>Reddit restricts api usage, per account, to <b>600 requests every 10 minutes.</b></p>
|
||||
<p><b>Depleted</b> is the estimated time until all 600 requests are used based on <b>Api Usage.</b></p>
|
||||
<p>API usage is sustainable when <b>Depleted</b> is greater than <b>Limit Reset</b></p>
|
||||
</span>
|
||||
<span>
|
||||
Depleted <svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<span>in ~<span id="apiDepletion"><%= data.apiDepletion %></span></span>
|
||||
<label>Limit Reset</label>
|
||||
<label>
|
||||
<span class="has-tooltip">
|
||||
<span style="margin-top:35px" class='tooltip rounded shadow-lg p-1 bg-gray-100 text-black space-y-3 p-2 text-left'>
|
||||
<p>Reddit restricts api usage, per account, to <b>600 requests every 10 minutes.</b></p>
|
||||
<p><b>Limit Reset</b> is the amount of time remaining in the current 10 minute period until the limit is reset to 600.</p>
|
||||
</span>
|
||||
<span>
|
||||
Limit Reset <svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<span class="has-tooltip">
|
||||
<span class='tooltip rounded shadow-lg p-1 bg-gray-100 text-black -mt-2'>
|
||||
<span id="limitReset"><%= data.limitReset %></span>
|
||||
@@ -264,8 +282,8 @@
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (data.name !== 'All') { %>
|
||||
<div class="bg-white shadow-md rounded my-3 dark:bg-gray-500 dark:text-white">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 dark:bg-gray-700 dark:text-white">
|
||||
<div class="bg-white shadow-md rounded my-3 bg-gray-600 ">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-700 ">
|
||||
<h4>Config
|
||||
<span>
|
||||
<span class="has-tooltip">
|
||||
@@ -316,8 +334,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="bg-white shadow-md rounded my-3 dark:bg-gray-500 dark:text-white">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-300 dark:bg-gray-700 dark:text-white">
|
||||
<div class="bg-white shadow-md rounded my-3 bg-gray-600 ">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-700 ">
|
||||
<h4>Usage</h4>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
@@ -440,10 +458,7 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -580,10 +595,7 @@
|
||||
class="h-4 w-4 inline-block cursor-help"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<use xlink:href="public/questionsymbol.svg#q" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
@@ -628,7 +640,6 @@
|
||||
<%- include('partials/footer') %>
|
||||
</div>
|
||||
<%- include('partials/instanceTabJs') %>
|
||||
<%- include('partials/themeJs') %>
|
||||
<%- include('partials/logSettingsJs') %>
|
||||
<script>
|
||||
window.sort = 'desc';
|
||||
|
||||
21
src/util.ts
21
src/util.ts
@@ -756,11 +756,11 @@ export const formatLogLineToHtml = (log: string | LogInfo) => {
|
||||
stripPrefix: false,
|
||||
sanitizeHtml: true,
|
||||
})
|
||||
.replace(/(\s*debug\s*):/i, '<span class="debug text-pink-400">$1</span>:')
|
||||
.replace(/(\s*warn\s*):/i, '<span class="warn text-yellow-400">$1</span>:')
|
||||
.replace(/(\s*info\s*):/i, '<span class="info text-blue-300">$1</span>:')
|
||||
.replace(/(\s*error\s*):/i, '<span class="error text-red-400">$1</span>:')
|
||||
.replace(/(\s*verbose\s*):/i, '<span class="error text-purple-400">$1</span>:')
|
||||
.replace(/(\s*debug\s*):/i, '<span class="debug blue">$1</span>:')
|
||||
.replace(/(\s*warn\s*):/i, '<span class="warn yellow">$1</span>:')
|
||||
.replace(/(\s*info\s*):/i, '<span class="info green">$1</span>:')
|
||||
.replace(/(\s*error\s*):/i, '<span class="error red">$1</span>:')
|
||||
.replace(/(\s*verbose\s*):/i, '<span class="error purple">$1</span>:')
|
||||
.replaceAll('\n', '<br />');
|
||||
//.replace(HYPERLINK_REGEX, '<a target="_blank" href="$&">$&</a>');
|
||||
return `<div class="logLine">${logContent}</div>`
|
||||
@@ -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, '\\$&');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user