fchat-rising/learn/profile-cache.ts

267 lines
7.5 KiB
TypeScript
Raw 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';
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;
}
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
}
if (false) {
console.log(`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-27 03:45:59 +00:00
isSafeImageURL(url: string): boolean {
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;
}
updateOverrides(c: ComplexCharacter): void {
const match = c.character.description.match(/\[url=(.*?)]\s*?Rising\s*?Portrait\s*?\[\/url]/i);
if (match && match[1]) {
const avatarUrl = match[1].trim();
if (!this.isSafeImageURL(avatarUrl)) {
return;
}
if (c.character.name === core.characters.ownCharacter.name) {
const parent = remote.getCurrentWindow().webContents;
parent.send('update-avatar-url', c.character.name, avatarUrl);
}
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) {
console.log(`Storing score 0 for character ${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
}
}