fchat-rising/learn/profile-cache.ts

280 lines
8.1 KiB
TypeScript
Raw Permalink Normal View History

2019-07-07 01:37:15 +00:00
import * as _ from 'lodash';
import core from '../chat/core';
2020-03-14 21:24:49 +00:00
import {Character as ComplexCharacter, CharacterGroup, Guestbook} from '../site/character_page/interfaces';
import { AsyncCache } from './async-cache';
2021-03-25 20:53:37 +00:00
import { Matcher, MatchReport } from './matcher';
2021-01-24 00:52:27 +00:00
import { PermanentIndexedStore } from './store/types';
2020-03-14 21:24:49 +00:00
import { CharacterImage, SimpleCharacter } from '../interfaces';
2021-03-25 20:53:37 +00:00
import { Scoring } from './matcher-types';
2022-01-01 00:06:08 +00:00
import { matchesSmartFilters } from './filter/smart-filter';
2024-01-27 03:45:59 +00:00
import * as remote from '@electron/remote';
2024-01-28 21:55:19 +00:00
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
2019-09-24 19:53:43 +00:00
export interface MetaRecord {
images: CharacterImage[] | null;
groups: CharacterGroup[] | null;
2020-03-14 21:24:49 +00:00
friends: SimpleCharacter[] | null;
guestbook: Guestbook | null;
2020-06-29 16:34:56 +00:00
lastMetaFetched: Date | null;
2019-09-24 19:53:43 +00:00
}
export interface CountRecord {
groupCount: number | null;
friendCount: number | null;
guestbookCount: number | null;
lastCounted: number | null;
}
2019-07-07 01:37:15 +00:00
2020-11-21 20:41:08 +00:00
export interface CharacterMatchSummary {
matchScore: number;
// dimensionsAtScoreLevel: number;
// dimensionsAboveScoreLevel: number;
// totalScoreDimensions: number;
searchScore: number;
2022-01-01 00:06:08 +00:00
isFiltered: boolean;
autoResponded?: boolean;
2020-11-21 20:41:08 +00:00
}
2019-07-07 01:37:15 +00:00
export interface CharacterCacheRecord {
character: ComplexCharacter;
2019-07-07 01:37:15 +00:00
lastFetched: Date;
added: Date;
2019-09-24 19:53:43 +00:00
// counts?: CountRecord;
2020-11-21 20:41:08 +00:00
match: CharacterMatchSummary;
2019-09-24 19:53:43 +00:00
meta?: MetaRecord;
2019-07-07 01:37:15 +00:00
}
export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
protected store?: PermanentIndexedStore;
2019-07-07 21:44:32 +00:00
protected lastFetch = Date.now();
2019-07-07 21:44:32 +00:00
setStore(store: PermanentIndexedStore): void {
2019-07-07 21:44:32 +00:00
this.store = store;
}
2022-01-01 00:06:08 +00:00
onEachInMemory(cb: (c: CharacterCacheRecord, key: string) => void): void {
_.each(this.cache, cb);
}
2019-07-15 16:59:16 +00:00
getSync(name: string): CharacterCacheRecord | null {
const key = AsyncCache.nameKey(name);
if (key in this.cache) {
return this.cache[key];
}
return null;
}
2024-01-29 01:21:09 +00:00
async get(name: string, skipStore: boolean = false, _fromChannel?: string): Promise<CharacterCacheRecord | null> {
const key = AsyncCache.nameKey(name);
2019-07-07 21:44:32 +00:00
if (key in this.cache) {
return this.cache[key];
2020-03-15 14:02:31 +00:00
}
if ((!this.store) || (skipStore)) {
return null;
2019-07-07 21:44:32 +00:00
}
2024-01-28 21:55:19 +00:00
// if (false) {
// log.info(`Retrieve '${name}' for channel '${fromChannel}, gap: ${(Date.now() - this.lastFetch)}ms`);
// this.lastFetch = Date.now();
// }
const pd = await this.store.getProfile(name);
2019-07-07 21:44:32 +00:00
if (!pd) {
return null;
}
const cacheRecord = await this.register(pd.profileData, true);
cacheRecord.lastFetched = new Date(pd.lastFetched * 1000);
cacheRecord.added = new Date(pd.firstSeen * 1000);
2019-09-24 19:53:43 +00:00
cacheRecord.meta = {
2021-09-10 23:02:50 +00:00
lastMetaFetched: pd.lastMetaFetched ? new Date(pd.lastMetaFetched * 1000) : null,
2019-09-24 19:53:43 +00:00
groups: pd.groups,
friends: pd.friends,
images: pd.images,
guestbook: pd.guestbook
};
/* cacheRecord.counts = {
lastCounted: pd.lastCounted,
groupCount: pd.groupCount,
friendCount: pd.friendCount,
guestbookCount: pd.guestbookCount
2019-09-24 19:53:43 +00:00
}; */
return cacheRecord;
2019-07-07 21:44:32 +00:00
}
2019-09-24 19:53:43 +00:00
// async registerCount(name: string, counts: CountRecord): Promise<void> {
// const record = await this.get(name);
//
// if (!record) {
// // coward's way out
// return;
// }
//
// record.counts = counts;
//
// if (this.store) {
// await this.store.updateProfileCounts(name, counts.guestbookCount, counts.friendCount, counts.groupCount);
// }
// }
async registerMeta(name: string, meta: MetaRecord): Promise<void> {
const record = await this.get(name);
if (!record) {
// coward's way out
return;
}
2019-09-24 19:53:43 +00:00
record.meta = meta;
if (this.store) {
2019-09-24 19:53:43 +00:00
await this.store.updateProfileMeta(name, meta.images, meta.guestbook, meta.friends, meta.groups);
}
}
2024-01-29 01:21:09 +00:00
static isSafeRisingPortraitURL(url: string): boolean {
2024-01-27 03:45:59 +00:00
if (url.match(/^https?:\/\/static\.f-list\.net\//i)) {
return true;
}
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?imgur\.com\//i)) {
return true;
}
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?freeimage\.host\//i)) {
return true;
}
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?iili\.io\//i)) {
return true;
}
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?redgifs\.com\//i)) {
return true;
}
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?e621\.net\//i)) {
return true;
}
return false;
}
2024-01-29 01:21:09 +00:00
static detectRisingPortraitURL(description: string): string | null {
if (!core.state.settings.risingShowHighQualityPortraits) {
return null;
}
const match = description.match(/\[url=(.*?)]\s*?Rising\s*?Portrait\s*?\[\/url]/i);
2024-01-27 03:45:59 +00:00
if (match && match[1]) {
2024-01-29 01:21:09 +00:00
return match[1].trim();
}
2024-01-27 03:45:59 +00:00
2024-01-29 01:21:09 +00:00
return null;
}
updateOverrides(c: ComplexCharacter): void {
const avatarUrl = ProfileCache.detectRisingPortraitURL(c.character.description);
if (avatarUrl) {
if (!ProfileCache.isSafeRisingPortraitURL(avatarUrl)) {
log.info('portrait.hq.invalid.domain', { name, url: avatarUrl });
2024-01-27 03:45:59 +00:00
return;
}
if (c.character.name === core.characters.ownCharacter.name) {
const parent = remote.getCurrentWindow().webContents;
parent.send('update-avatar-url', c.character.name, avatarUrl);
}
2024-01-28 22:00:59 +00:00
log.info('portrait.hq.url', { name: c.character.name, url: avatarUrl });
2024-01-27 03:45:59 +00:00
core.characters.setOverride(c.character.name, 'avatarUrl', avatarUrl);
}
}
async register(c: ComplexCharacter, skipStore: boolean = false): Promise<CharacterCacheRecord> {
const k = AsyncCache.nameKey(c.character.name);
2020-11-21 20:41:08 +00:00
const match = ProfileCache.match(c);
2023-03-15 01:28:50 +00:00
let score = (!match || match.score === null) ? Scoring.NEUTRAL : match.score;
2019-07-07 01:37:15 +00:00
if (score === 0) {
2024-01-28 21:55:19 +00:00
log.info('cache.profile.store.zero.score', { name: c.character.name });
}
2024-01-27 03:45:59 +00:00
this.updateOverrides(c);
2020-11-21 20:41:08 +00:00
// const totalScoreDimensions = match ? Matcher.countScoresTotal(match) : 0;
// const dimensionsAtScoreLevel = match ? (Matcher.countScoresAtLevel(match, score) || 0) : 0;
// const dimensionsAboveScoreLevel = match ? (Matcher.countScoresAboveLevel(match, Math.max(score, Scoring.WEAK_MATCH))) : 0;
2022-01-02 22:37:57 +00:00
const risingFilter = core.state.settings.risingFilter;
const isFiltered = matchesSmartFilters(c.character, risingFilter);
2022-01-01 00:06:08 +00:00
2023-03-15 01:28:50 +00:00
const penalty = (isFiltered && risingFilter.penalizeMatches) ? -5 : (!isFiltered && risingFilter.rewardNonMatches) ? 2 : 0;
if (isFiltered && risingFilter.penalizeMatches) {
score = Scoring.MISMATCH;
}
2022-01-01 00:06:08 +00:00
const searchScore = match
2023-03-15 01:28:50 +00:00
? Matcher.calculateSearchScoreForMatch(score, match, penalty)
2022-01-01 00:06:08 +00:00
: 0;
const matchDetails = { matchScore: score, searchScore, isFiltered };
2020-11-21 20:41:08 +00:00
2019-07-07 21:44:32 +00:00
if ((this.store) && (!skipStore)) {
await this.store.storeProfile(c);
2019-07-07 21:44:32 +00:00
}
2019-07-07 01:37:15 +00:00
if (k in this.cache) {
const rExisting = this.cache[k];
rExisting.character = c;
rExisting.lastFetched = new Date();
2020-11-21 20:41:08 +00:00
rExisting.match = matchDetails;
2019-07-07 01:37:15 +00:00
return rExisting;
}
const rNew = {
character: c,
lastFetched: new Date(),
added: new Date(),
2020-11-21 20:41:08 +00:00
match: matchDetails
2019-07-07 01:37:15 +00:00
};
this.cache[k] = rNew;
return rNew;
}
2020-11-21 20:41:08 +00:00
static match(c: ComplexCharacter): MatchReport | null {
2019-07-07 01:37:15 +00:00
const you = core.characters.ownProfile;
if (!you) {
2020-11-21 20:41:08 +00:00
return null;
}
2020-11-21 20:41:08 +00:00
return Matcher.identifyBestMatchReport(you.character, c.character);
2019-07-07 01:37:15 +00:00
}
}