mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-04-19 03:00:07 -04:00
Use more robust caching implementation
* use node-cache-manager so operator has a choice of memory or redis * BC update TTL values to be in seconds instead of milliseconds * Count requests and misses for cache * display cache stats in ui
This commit is contained in:
39
package-lock.json
generated
39
package-lock.json
generated
@@ -49,6 +49,7 @@
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@types/async": "^3.2.7",
|
||||
"@types/cache-manager": "^3.4.2",
|
||||
"@types/cache-manager-redis-store": "^2.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/express-socket.io-session": "^1.3.6",
|
||||
@@ -194,6 +195,16 @@
|
||||
"integrity": "sha512-1IwA74t5ID4KWo0Kndal16MhiPSZgMe1fGc+MLT6j5r+Ab7jku36PFTl4PP6MiWw0BJscM9QpZEo00qixNQoRg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/cache-manager-redis-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cache-manager-redis-store/-/cache-manager-redis-store-2.0.0.tgz",
|
||||
"integrity": "sha512-6svzdqXFcjsBPtTjkrLinlcB/XsIY4kkVh7KgFYjJHqw0usg2nALXN9YNe7q2r7RErymoCAvnnlNqjwTd3k/sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/cache-manager": "*",
|
||||
"@types/redis": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/command-line-args": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.0.0.tgz",
|
||||
@@ -395,6 +406,15 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/redis": {
|
||||
"version": "2.8.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.31.tgz",
|
||||
"integrity": "sha512-daWrrTDYaa5iSDFbgzZ9gOOzyp2AJmYK59OlG/2KGBgYWF3lfs8GDKm1c//tik5Uc93hDD36O+qLPvzDolChbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "1.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||
@@ -3652,6 +3672,16 @@
|
||||
"integrity": "sha512-1IwA74t5ID4KWo0Kndal16MhiPSZgMe1fGc+MLT6j5r+Ab7jku36PFTl4PP6MiWw0BJscM9QpZEo00qixNQoRg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/cache-manager-redis-store": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cache-manager-redis-store/-/cache-manager-redis-store-2.0.0.tgz",
|
||||
"integrity": "sha512-6svzdqXFcjsBPtTjkrLinlcB/XsIY4kkVh7KgFYjJHqw0usg2nALXN9YNe7q2r7RErymoCAvnnlNqjwTd3k/sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/cache-manager": "*",
|
||||
"@types/redis": "*"
|
||||
}
|
||||
},
|
||||
"@types/command-line-args": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.0.0.tgz",
|
||||
@@ -3852,6 +3882,15 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/redis": {
|
||||
"version": "2.8.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.31.tgz",
|
||||
"integrity": "sha512-daWrrTDYaa5iSDFbgzZ9gOOzyp2AJmYK59OlG/2KGBgYWF3lfs8GDKm1c//tik5Uc93hDD36O+qLPvzDolChbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/serve-static": {
|
||||
"version": "1.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@types/async": "^3.2.7",
|
||||
"@types/cache-manager": "^3.4.2",
|
||||
"@types/cache-manager-redis-store": "^2.0.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.4",
|
||||
"@types/express-socket.io-session": "^1.3.6",
|
||||
|
||||
@@ -109,8 +109,7 @@ export class App {
|
||||
}
|
||||
} = config;
|
||||
|
||||
CacheManager.authorTTL = argParseInt(authorTTL);
|
||||
CacheManager.enabled = store !== 'none';
|
||||
CacheManager.setDefaultsFromConfig(config);
|
||||
|
||||
this.dryRun = parseBool(dryRun) === true ? true : undefined;
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
|
||||
@@ -379,26 +379,28 @@ export interface PollingOptions extends PollingDefaults {
|
||||
|
||||
export interface SubredditCacheConfig {
|
||||
/**
|
||||
* Amount of time, in milliseconds, author activities (Comments/Submission) should be cached
|
||||
* @examples [10000]
|
||||
* @default 10000
|
||||
* Amount of time, in seconds, author activities (Comments/Submission) should be cached
|
||||
* @examples [60]
|
||||
* @default 60
|
||||
* */
|
||||
authorTTL?: number;
|
||||
/**
|
||||
* Amount of time, in milliseconds, wiki content pages should be cached
|
||||
* @examples [300000]
|
||||
* @default 300000
|
||||
* Amount of time, in seconds, wiki content pages should be cached
|
||||
* @examples [300]
|
||||
* @default 300
|
||||
* */
|
||||
wikiTTL?: number;
|
||||
|
||||
/**
|
||||
* Amount of time, in milliseconds, [Toolbox User Notes](https://www.reddit.com/r/toolbox/wiki/docs/usernotes) should be cached
|
||||
* @examples [60000]
|
||||
* @default 60000
|
||||
* @examples [300]
|
||||
* @default 300
|
||||
* */
|
||||
userNotesTTL?: number;
|
||||
}
|
||||
|
||||
export type StrongSubredditCacheConfig = Required<SubredditCacheConfig>;
|
||||
|
||||
export interface Footer {
|
||||
/**
|
||||
* Customize the footer for Actions that send replies (Comment/Ban)
|
||||
@@ -459,7 +461,7 @@ export interface ManagerOptions {
|
||||
/**
|
||||
* Per-subreddit config for caching TTL values. If set to `false` caching is disabled.
|
||||
* */
|
||||
caching?: false | SubredditCacheConfig
|
||||
caching?: SubredditCacheConfig
|
||||
|
||||
/**
|
||||
* Use this option to override the `dryRun` setting for all `Checks`
|
||||
@@ -766,3 +768,12 @@ export interface OperatorConfig extends OperatorJsonConfig {
|
||||
}
|
||||
|
||||
//export type OperatorConfig = Required<OperatorJsonConfig>;
|
||||
|
||||
interface CacheTypeStat {
|
||||
requests: number,
|
||||
miss: number,
|
||||
}
|
||||
|
||||
export interface ResourceStats {
|
||||
[key: string]: CacheTypeStat
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ export const parseOpConfigFromArgs = (args: any): OperatorJsonConfig => {
|
||||
clientSecret,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
redirectUri,
|
||||
wikiConfig,
|
||||
dryRun,
|
||||
heartbeat,
|
||||
@@ -278,7 +279,8 @@ export const parseOpConfigFromArgs = (args: any): OperatorJsonConfig => {
|
||||
clientId,
|
||||
clientSecret,
|
||||
accessToken,
|
||||
refreshToken
|
||||
refreshToken,
|
||||
redirectUri,
|
||||
},
|
||||
subreddits: {
|
||||
names: subreddits,
|
||||
@@ -342,6 +344,7 @@ export const parseOpConfigFromEnv = (): OperatorJsonConfig => {
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
accessToken: process.env.ACCESS_TOKEN,
|
||||
refreshToken: process.env.REFRESH_TOKEN,
|
||||
redirectUri: process.env.REDIRECT_URI,
|
||||
},
|
||||
subreddits: {
|
||||
names: subs,
|
||||
@@ -469,7 +472,7 @@ export const buildOperatorConfigWithDefaults = (data: OperatorJsonConfig): Opera
|
||||
} = {},
|
||||
snoowrap = {},
|
||||
web: {
|
||||
port = 5058,
|
||||
port = 8085,
|
||||
sessionSecret,
|
||||
maxLogs = 200,
|
||||
} = {},
|
||||
@@ -486,7 +489,7 @@ export const buildOperatorConfigWithDefaults = (data: OperatorJsonConfig): Opera
|
||||
} = data;
|
||||
|
||||
const cacheOptDefaults = {ttl: 60, max: 500};
|
||||
const cacheDefaults = {authorTTL: 60000, userNotesTTL: 60000, wikiTTL: 300000};
|
||||
const cacheDefaults = {authorTTL: 60, userNotesTTL: 300, wikiTTL: 300};
|
||||
|
||||
let cache = {
|
||||
...cacheDefaults,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import {Manager} from "../Subreddit/Manager";
|
||||
import {getLogger} from "../Utils/loggerFactory";
|
||||
import LoggedError from "../Utils/LoggedError";
|
||||
import {OperatorConfig, RUNNING, STOPPED, SYSTEM, USER} from "../Common/interfaces";
|
||||
import {OperatorConfig, ResourceStats, RUNNING, STOPPED, SYSTEM, USER} from "../Common/interfaces";
|
||||
|
||||
const MemoryStore = createMemoryStore(session);
|
||||
const app = addAsync(express());
|
||||
@@ -163,7 +163,7 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
if (operator !== undefined && socket.handshake.session.user.toLowerCase() === operator.toLowerCase()) {
|
||||
if (name !== undefined && socket.handshake.session.user.toLowerCase() === name.toLowerCase()) {
|
||||
// @ts-ignore
|
||||
operatorSessionId = socket.handshake.session.id;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
|
||||
req.session['user'] = user;
|
||||
// @ts-ignore
|
||||
req.session['subreddits'] = operator !== undefined && operator.toLowerCase() === user.toLowerCase() ? bot.subManagers.map(x => x.displayLabel) : subs.reduce((acc: string[], x) => {
|
||||
req.session['subreddits'] = name !== undefined && name.toLowerCase() === user.toLowerCase() ? bot.subManagers.map(x => x.displayLabel) : subs.reduce((acc: string[], x) => {
|
||||
const sm = bot.subManagers.find(y => y.subreddit.display_name === x.display_name);
|
||||
if (sm !== undefined) {
|
||||
return acc.concat(sm.displayLabel);
|
||||
@@ -277,8 +277,14 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
return res.render('noSubs', {operatorDisplay: display});
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const logs = filterLogBySubreddit(subLogMap, req.session.subreddits, {level, operator, user, sort, limit});
|
||||
const logs = filterLogBySubreddit(subLogMap, req.session.subreddits, {
|
||||
level,
|
||||
operator: isOperator,
|
||||
user,
|
||||
// @ts-ignore
|
||||
sort,
|
||||
limit
|
||||
});
|
||||
const subManagerData = [];
|
||||
for (const s of subreddits) {
|
||||
const m = bot.subManagers.find(x => x.displayLabel === s) as Manager;
|
||||
@@ -309,6 +315,18 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
startedAt: 'Not Started',
|
||||
startedAtHuman: 'Not Started',
|
||||
delayBy: m.delayBy === undefined ? 'No' : `Delayed by ${m.delayBy} sec`,
|
||||
cache: {
|
||||
//currentKeyCount: await m.resources.getCacheKeyCount(),
|
||||
totalRequests: Object.values(m.resources.stats.cache).reduce((acc, curr) => acc + curr.requests, 0),
|
||||
types: {
|
||||
...Object.keys(m.resources.stats.cache).reduce((acc, curr) => {
|
||||
const per = acc[curr].miss === 0 ? 0 : formatNumber(acc[curr].miss / acc[curr].requests) * 100;
|
||||
// @ts-ignore
|
||||
acc[curr].missPercent = `${per}%`;
|
||||
return acc;
|
||||
}, m.resources.stats.cache),
|
||||
},
|
||||
}
|
||||
};
|
||||
// TODO replace indicator data with js on client page
|
||||
let indicator;
|
||||
@@ -355,6 +373,25 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
});
|
||||
const {checks, ...rest} = totalStats;
|
||||
|
||||
const resCum: ResourceStats = {
|
||||
author: {requests: 0, miss: 0},
|
||||
authorCrit: {requests: 0, miss: 0},
|
||||
content: {requests: 0, miss: 0}
|
||||
};
|
||||
|
||||
let cumRaw = subManagerData.reduce((acc, curr) => {
|
||||
Object.keys(curr.cache.types as ResourceStats).forEach((k) => {
|
||||
acc[k].requests += curr.cache.types[k].requests;
|
||||
acc[k].miss += curr.cache.types[k].miss;
|
||||
});
|
||||
return acc;
|
||||
}, resCum);
|
||||
cumRaw = Object.keys(cumRaw).reduce((acc, curr) => {
|
||||
const per = acc[curr].miss === 0 ? 0 : formatNumber(acc[curr].miss / acc[curr].requests) * 100;
|
||||
// @ts-ignore
|
||||
acc[curr].missPercent = `${per}%`;
|
||||
return acc;
|
||||
}, cumRaw);
|
||||
let allManagerData: any = {
|
||||
name: 'All',
|
||||
botState: {
|
||||
@@ -367,6 +404,14 @@ const rcbServer = async function (options: OperatorConfig) {
|
||||
softLimit: bot.softLimit,
|
||||
hardLimit: bot.hardLimit,
|
||||
stats: rest,
|
||||
cache: {
|
||||
// naive
|
||||
currentKeyCount: await bot.subManagers[0].resources.getCacheKeyCount(),
|
||||
totalRequests: subManagerData.reduce((acc, curr) => acc + curr.cache.totalRequests, 0),
|
||||
types: {
|
||||
...cumRaw
|
||||
}
|
||||
}
|
||||
};
|
||||
if (allManagerData.logs === undefined) {
|
||||
// this should happen but saw an edge case where potentially did
|
||||
|
||||
@@ -295,6 +295,14 @@
|
||||
|
||||
<label>Actions</label>
|
||||
<span><%= data.stats.actionsRunTotal %> Run</span>
|
||||
<% if (data.name === 'All') { %>
|
||||
<label>Cached Keys</label>
|
||||
<span><%= data.cache.currentKeyCount %></span>
|
||||
<% } %>
|
||||
<label>Total Cache Reqs</label>
|
||||
<span><%= data.cache.totalRequests %></span>
|
||||
<label>Author Cache</label>
|
||||
<span><%= data.cache.types.author.requests %> (<%= data.cache.types.author.missPercent %> Miss)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,14 +12,21 @@ import winston, {Logger} from "winston";
|
||||
import fetch from 'node-fetch';
|
||||
import {mergeArr, parseExternalUrl, parseWikiContext} from "../util";
|
||||
import LoggedError from "../Utils/LoggedError";
|
||||
import {Footer, SubredditCacheConfig} from "../Common/interfaces";
|
||||
import {
|
||||
CacheOptions, CacheProvider,
|
||||
Footer, OperatorConfig, ResourceStats,
|
||||
StrongCache,
|
||||
StrongSubredditCacheConfig,
|
||||
SubredditCacheConfig
|
||||
} from "../Common/interfaces";
|
||||
import UserNotes from "./UserNotes";
|
||||
import Mustache from "mustache";
|
||||
import he from "he";
|
||||
import {AuthorCriteria} from "../Author/Author";
|
||||
import Poll from "snoostorm/out/util/Poll";
|
||||
import {SPoll} from "./Streams";
|
||||
import cacheManager from 'cache-manager';
|
||||
import cacheManager, {Cache} from 'cache-manager';
|
||||
import redisStore from 'cache-manager-redis-store';
|
||||
|
||||
export const DEFAULT_FOOTER = '\r\n*****\r\nThis action was performed by [a bot.]({{botLink}}) Mention a moderator or [send a modmail]({{modmailLink}}) if you any ideas, questions, or concerns about this action.';
|
||||
|
||||
@@ -29,14 +36,38 @@ export interface SubredditResourceOptions extends SubredditCacheConfig, Footer {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export interface SubredditResourceSetOptions extends SubredditCacheConfig, Footer {
|
||||
enabled: boolean;
|
||||
interface StrongSubredditResourceOptions extends SubredditResourceOptions {
|
||||
cache?: Cache
|
||||
}
|
||||
|
||||
//const memoryCache = cacheManager.caching({store: 'memory', max: 1000, ttl: 60/*seconds*/});
|
||||
export interface SubredditResourceSetOptions extends SubredditCacheConfig, Footer {
|
||||
enabled: boolean,
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// interface ResourceStats {
|
||||
// cache: {
|
||||
// //keys: number,
|
||||
// author: {
|
||||
// requests: number,
|
||||
// miss: number,
|
||||
// },
|
||||
// authorCrit: {
|
||||
// requests: number,
|
||||
// miss: number,
|
||||
// }
|
||||
// content: {
|
||||
// requests: number,
|
||||
// miss: number,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
export class SubredditResources {
|
||||
enabled!: boolean;
|
||||
//enabled!: boolean;
|
||||
protected authorTTL!: number;
|
||||
protected useSubredditAuthorCache!: boolean;
|
||||
protected wikiTTL!: number;
|
||||
@@ -45,15 +76,20 @@ export class SubredditResources {
|
||||
userNotes: UserNotes;
|
||||
footer!: false | string;
|
||||
subreddit: Subreddit
|
||||
cache?: Cache
|
||||
|
||||
constructor(name: string, options: SubredditResourceOptions) {
|
||||
stats: { cache: ResourceStats };
|
||||
|
||||
constructor(name: string, options: StrongSubredditResourceOptions) {
|
||||
const {
|
||||
subreddit,
|
||||
logger,
|
||||
enabled = true,
|
||||
userNotesTTL = 60000,
|
||||
cache,
|
||||
} = options || {};
|
||||
|
||||
this.cache = cache;
|
||||
this.subreddit = subreddit;
|
||||
this.name = name;
|
||||
if (logger === undefined) {
|
||||
@@ -63,21 +99,36 @@ export class SubredditResources {
|
||||
this.logger = logger.child({labels: ['Resource Cache']}, mergeArr);
|
||||
}
|
||||
|
||||
this.stats = {
|
||||
cache: {
|
||||
author: {
|
||||
requests: 0,
|
||||
miss: 0,
|
||||
},
|
||||
authorCrit: {
|
||||
requests: 0,
|
||||
miss: 0,
|
||||
},
|
||||
content: {
|
||||
requests: 0,
|
||||
miss: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.userNotes = new UserNotes(enabled ? userNotesTTL : 0, this.subreddit, this.logger)
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
setOptions (options: SubredditResourceSetOptions) {
|
||||
setOptions(options: SubredditResourceSetOptions) {
|
||||
const {
|
||||
enabled = true,
|
||||
authorTTL = 10000,
|
||||
userNotesTTL = 60000,
|
||||
wikiTTL = 300000, // 5 minutes
|
||||
authorTTL,
|
||||
userNotesTTL,
|
||||
wikiTTL,
|
||||
footer = DEFAULT_FOOTER
|
||||
} = options || {};
|
||||
|
||||
this.footer = footer;
|
||||
this.enabled = manager.enabled ? enabled : false;
|
||||
if (authorTTL === undefined) {
|
||||
this.useSubredditAuthorCache = false;
|
||||
this.authorTTL = manager.authorTTL;
|
||||
@@ -85,35 +136,40 @@ export class SubredditResources {
|
||||
this.useSubredditAuthorCache = true;
|
||||
this.authorTTL = authorTTL;
|
||||
}
|
||||
this.wikiTTL = wikiTTL;
|
||||
this.userNotes.notesTTL = enabled ? userNotesTTL : 0;
|
||||
this.wikiTTL = wikiTTL || this.wikiTTL;
|
||||
this.userNotes.notesTTL = userNotesTTL || this.userNotes.notesTTL;
|
||||
}
|
||||
|
||||
async getCacheKeyCount() {
|
||||
if (this.cache !== undefined && this.cache.store.keys !== undefined) {
|
||||
return (await this.cache.store.keys()).length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async getAuthorActivities(user: RedditUser, options: AuthorTypedActivitiesOptions): Promise<Array<Submission | Comment>> {
|
||||
const useCache = this.enabled && this.authorTTL > 0;
|
||||
let hash;
|
||||
if (useCache) {
|
||||
if (this.cache !== undefined && this.authorTTL > 0) {
|
||||
const userName = user.name;
|
||||
const hashObj: any = {...options, userName};
|
||||
if (this.useSubredditAuthorCache) {
|
||||
hashObj.subreddit = this.name;
|
||||
}
|
||||
hash = objectHash.sha1({...options, userName});
|
||||
const hash = objectHash.sha1({...options, userName});
|
||||
|
||||
const cacheVal = cache.get(hash);
|
||||
if (null !== cacheVal) {
|
||||
this.stats.cache.author.requests++;
|
||||
let miss = false;
|
||||
const cacheVal = await this.cache.wrap(hash, async () => {
|
||||
miss = true;
|
||||
return await getAuthorActivities(user, options);
|
||||
}, {ttl: this.authorTTL});
|
||||
if (!miss) {
|
||||
this.logger.debug(`Cache Hit: ${userName} (${options.type || 'overview'})`);
|
||||
return cacheVal as Array<Submission | Comment>;
|
||||
} else {
|
||||
this.stats.cache.author.miss++;
|
||||
}
|
||||
return cacheVal as Array<Submission | Comment>;
|
||||
}
|
||||
|
||||
|
||||
const items = await getAuthorActivities(user, options);
|
||||
|
||||
if (useCache) {
|
||||
cache.put(hash, items, this.authorTTL);
|
||||
}
|
||||
return Promise.resolve(items);
|
||||
return await getAuthorActivities(user, options);
|
||||
}
|
||||
|
||||
async getAuthorComments(user: RedditUser, options: AuthorActivitiesOptions): Promise<Comment[]> {
|
||||
@@ -143,14 +199,13 @@ export class SubredditResources {
|
||||
return val;
|
||||
}
|
||||
|
||||
const useCache = this.enabled && this.wikiTTL > 0;
|
||||
// try to get cached value first
|
||||
let hash = `${subreddit.display_name}-${cacheKey}`;
|
||||
if (useCache) {
|
||||
const cachedContent = cache.get(hash);
|
||||
if (this.cache !== undefined && this.wikiTTL > 0) {
|
||||
const cachedContent = await this.cache.get(hash);
|
||||
if (cachedContent !== null) {
|
||||
this.logger.debug(`Cache Hit: ${cacheKey}`);
|
||||
return cachedContent;
|
||||
return cachedContent as string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,37 +240,34 @@ export class SubredditResources {
|
||||
}
|
||||
}
|
||||
|
||||
if (useCache) {
|
||||
cache.put(hash, wikiContent, this.wikiTTL);
|
||||
if (this.cache !== undefined && this.wikiTTL > 0) {
|
||||
this.cache.set(hash, wikiContent, this.wikiTTL);
|
||||
}
|
||||
|
||||
return wikiContent;
|
||||
}
|
||||
|
||||
async testAuthorCriteria(item: (Comment | Submission), authorOpts: AuthorCriteria, include = true) {
|
||||
const useCache = this.enabled && this.authorTTL > 0;
|
||||
let hash;
|
||||
if (useCache) {
|
||||
if (this.cache !== undefined && this.authorTTL > 0) {
|
||||
const hashObj = {itemId: item.id, ...authorOpts, include};
|
||||
hash = `authorCrit-${objectHash.sha1(hashObj)}`;
|
||||
const cachedAuthorTest = cache.get(hash);
|
||||
if (null !== cachedAuthorTest) {
|
||||
const hash = `authorCrit-${objectHash.sha1(hashObj)}`;
|
||||
let miss = false;
|
||||
const cachedAuthorTest = await this.cache.wrap(hash, async () => {
|
||||
miss = true;
|
||||
return await testAuthorCriteria(item, authorOpts, include, this.userNotes);
|
||||
}, {ttl: this.authorTTL});
|
||||
if (!miss) {
|
||||
this.logger.debug(`Cache Hit: Author Check on ${item.id}`);
|
||||
return cachedAuthorTest;
|
||||
}
|
||||
return cachedAuthorTest;
|
||||
}
|
||||
|
||||
const result = await testAuthorCriteria(item, authorOpts, include, this.userNotes);
|
||||
if (useCache) {
|
||||
cache.put(hash, result, this.authorTTL);
|
||||
}
|
||||
return result;
|
||||
return await testAuthorCriteria(item, authorOpts, include, this.userNotes);
|
||||
}
|
||||
|
||||
async generateFooter(item: Submission | Comment, actionFooter?: false | string)
|
||||
{
|
||||
async generateFooter(item: Submission | Comment, actionFooter?: false | string) {
|
||||
let footer = actionFooter !== undefined ? actionFooter : this.footer;
|
||||
if(footer === false) {
|
||||
if (footer === false) {
|
||||
return '';
|
||||
}
|
||||
const subName = await item.subreddit.display_name;
|
||||
@@ -227,11 +279,54 @@ export class SubredditResources {
|
||||
}
|
||||
}
|
||||
|
||||
export const createCacheManager = (options: CacheOptions) => {
|
||||
const {store, max, ttl = 60, host = 'localhost', port, auth_pass, db} = options;
|
||||
switch (store) {
|
||||
case 'none':
|
||||
return undefined;
|
||||
case 'redis':
|
||||
return cacheManager.caching({
|
||||
store: redisStore,
|
||||
host,
|
||||
port,
|
||||
auth_pass,
|
||||
db,
|
||||
ttl
|
||||
});
|
||||
case 'memory':
|
||||
default:
|
||||
return cacheManager.caching({store: 'memory', max, ttl});
|
||||
}
|
||||
}
|
||||
|
||||
class SubredditResourcesManager {
|
||||
resources: Map<string, SubredditResources> = new Map();
|
||||
authorTTL: number = 10000;
|
||||
enabled: boolean = true;
|
||||
modStreams: Map<string, SPoll<Snoowrap.Submission | Snoowrap.Comment>> = new Map();
|
||||
defaultCache?: Cache;
|
||||
ttlDefaults!: StrongSubredditCacheConfig;
|
||||
|
||||
setDefaultsFromConfig(config: OperatorConfig) {
|
||||
const {
|
||||
caching: {
|
||||
authorTTL,
|
||||
userNotesTTL,
|
||||
wikiTTL,
|
||||
provider,
|
||||
},
|
||||
} = config;
|
||||
this.setDefaultCache(provider);
|
||||
this.setTTLDefaults({authorTTL, userNotesTTL, wikiTTL});
|
||||
}
|
||||
|
||||
setDefaultCache(options: CacheOptions) {
|
||||
this.defaultCache = createCacheManager(options);
|
||||
}
|
||||
|
||||
setTTLDefaults(def: StrongSubredditCacheConfig) {
|
||||
this.ttlDefaults = def;
|
||||
}
|
||||
|
||||
get(subName: string): SubredditResources | undefined {
|
||||
if (this.resources.has(subName)) {
|
||||
@@ -241,7 +336,7 @@ class SubredditResourcesManager {
|
||||
}
|
||||
|
||||
set(subName: string, initOptions: SubredditResourceOptions): SubredditResources {
|
||||
const resource = new SubredditResources(subName, initOptions);
|
||||
const resource = new SubredditResources(subName, {...this.ttlDefaults, ...initOptions, cache: this.defaultCache});
|
||||
this.resources.set(subName, resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export const clientId = new commander.Option('-i, --clientId <id>', 'Client ID f
|
||||
|
||||
export const clientSecret = new commander.Option('-e, --clientSecret <secret>', 'Client Secret for your Reddit application (default: process.env.CLIENT_SECRET)');
|
||||
|
||||
export const redirectURI = new commander.Option('-u, --redirectUri <uri>', 'Redirect URI for your Reddit application (default: process.env.REDIRECT_URI)');
|
||||
export const redirectUri = new commander.Option('-u, --redirectUri <uri>', 'Redirect URI for your Reddit application (default: process.env.REDIRECT_URI)');
|
||||
|
||||
export const sessionSecret = new commander.Option('-t, --sessionSecret <secret>', 'Secret use to encrypt session id/data (default: process.env.SESSION_SECRET)');
|
||||
|
||||
@@ -67,7 +67,7 @@ export const getUniversalWebOptions = (): commander.Option[] => {
|
||||
clientSecret,
|
||||
createAccessTokenOption(),
|
||||
createRefreshTokenOption(),
|
||||
redirectURI,
|
||||
redirectUri,
|
||||
sessionSecret,
|
||||
subreddits,
|
||||
logDir,
|
||||
|
||||
Reference in New Issue
Block a user