diff --git a/chat/ConversationView.vue b/chat/ConversationView.vue index 0bdcc20..46ffa67 100644 --- a/chat/ConversationView.vue +++ b/chat/ConversationView.vue @@ -595,48 +595,54 @@ - .message.message-score { - padding-left: 5px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - - &.match { - border-left: 12px solid #027b02; - background-color: rgba(1, 76, 1, 0.45); + .message { + &.message-event { + font-size: 85%; + background-color: rgba(255, 255, 255, 0.1); } - &.weak-match { - border-left: 12px solid #015a01; - background-color: rgba(0, 58, 0, 0.35); - } + &.message-score { + padding-left: 5px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); - &.weak-mismatch { - background-color: rgba(208, 188, 0, 0.0); - border-left: 12px solid rgb(138, 123, 0); - - .bbcode { - filter: grayscale(0.7); + &.match { + border-left: 12px solid #027b02; + background-color: rgba(1, 76, 1, 0.45); } - .bbcode, - .user-view, - .message-time { - opacity: 0.4; - } - } - - &.mismatch { - border-left: 12px solid #841a1a; - - .bbcode { - filter: grayscale(0.8); + &.weak-match { + border-left: 12px solid #015a01; + background-color: rgba(0, 58, 0, 0.35); } - .bbcode, - .user-view, - .message-time { - opacity: 0.3; + &.weak-mismatch { + background-color: rgba(208, 188, 0, 0.0); + border-left: 12px solid rgb(138, 123, 0); + + .bbcode { + filter: grayscale(0.7); + } + + .bbcode, + .user-view, + .message-time { + opacity: 0.4; + } + } + + &.mismatch { + border-left: 12px solid #841a1a; + + .bbcode { + filter: grayscale(0.8); + } + + .bbcode, + .user-view, + .message-time { + opacity: 0.3; + } } } } - \ No newline at end of file diff --git a/chat/common.ts b/chat/common.ts index 1674b03..d1496d0 100644 --- a/chat/common.ts +++ b/chat/common.ts @@ -94,7 +94,7 @@ export class Message implements Conversation.ChatMessage { readonly id = ++messageId; isHighlight = false; - score?: number; + score = 0; constructor(readonly type: Conversation.Message.Type, readonly sender: Character, readonly text: string, readonly time: Date = new Date()) { @@ -106,6 +106,8 @@ export class EventMessage implements Conversation.EventMessage { readonly id = ++messageId; readonly type = Conversation.Message.Type.Event; + readonly score = 0; + constructor(readonly text: string, readonly time: Date = new Date()) { } } \ No newline at end of file diff --git a/chat/interfaces.ts b/chat/interfaces.ts index d16c2f9..7114388 100644 --- a/chat/interfaces.ts +++ b/chat/interfaces.ts @@ -14,7 +14,7 @@ export namespace Conversation { readonly text: string readonly time: Date - score?: number; + score: number; } export interface EventMessage extends BaseMessage { diff --git a/chat/message_view.ts b/chat/message_view.ts index c14fb0c..04270fa 100644 --- a/chat/message_view.ts +++ b/chat/message_view.ts @@ -1,7 +1,7 @@ -import { Component, Prop, Watch } from '@f-list/vue-ts'; +import { Component, Hook, Prop } from '@f-list/vue-ts'; import {CreateElement, default as Vue, VNode, VNodeChildrenArrayContents} from 'vue'; import {Channel} from '../fchat'; -import { Score, Scoring } from '../site/character_page/matcher'; +import { Score, Scoring } from '../learn/matcher'; import {BBCodeView} from './bbcode'; import {formatTime} from './common'; import core from './core'; @@ -16,6 +16,15 @@ const userPostfix: {[key: number]: string | undefined} = { @Component({ render(this: MessageView, createElement: CreateElement): VNode { const message = this.message; + + // setTimeout( + // () => { + // console.log('Now scoring high!', message.text.substr(0, 64)); + // message.score = Scoring.MATCH; + // }, + // 5000 + // ); + const children: VNodeChildrenArrayContents = [createElement('span', {staticClass: 'message-time'}, `[${formatTime(message.time)}] `)]; const separators = core.connection.isOpen ? core.state.settings.messageSeparators : false; @@ -59,22 +68,47 @@ export default class MessageView extends Vue { scoreClasses = this.getMessageScoreClasses(this.message); - @Watch('message.score') + scoreWatcher: (() => void) | null = ((this.message.type === Conversation.Message.Type.Ad) && (this.message.score === 0)) + ? this.$watch('message.score', () => this.scoreUpdate()) + : null; + + + @Hook('beforeDestroy') + onBeforeDestroy(): void { + console.log('onbeforedestroy'); + + if (this.scoreWatcher) { + console.log('onbeforedestroy killed'); + + this.scoreWatcher(); // stop watching + this.scoreWatcher = null; + } + } + + // @Watch('message.score') scoreUpdate(): void { - console.log('Message score update', this.message.score, this.message.text); + const oldClasses = this.scoreClasses; this.scoreClasses = this.getMessageScoreClasses(this.message); - this.$forceUpdate(); + if (this.scoreClasses !== oldClasses) { + this.$forceUpdate(); + } + + if (this.scoreWatcher) { + console.log('watch killed'); + + this.scoreWatcher(); // stop watching + this.scoreWatcher = null; + } } - getMessageScoreClasses(message: Conversation.Message): string { - if ((!('score' in message)) || (message.score === undefined) || (message.score === 0)) { + if (message.score === 0) { return ''; } - console.log('Score was', message.score); + // console.log('Score was', message.score); return `message-score ${Score.getClasses(message.score as Scoring)}`; diff --git a/electron/Window.vue b/electron/Window.vue index 209faf4..9719733 100644 --- a/electron/Window.vue +++ b/electron/Window.vue @@ -84,6 +84,7 @@ @Hook('mounted') mounted(): void { + // top bar devtools // browserWindow.webContents.openDevTools(); this.addTab(); @@ -196,7 +197,8 @@ tray.on('click', (_) => this.trayClicked(tab)); const view = new electron.remote.BrowserView(); - // view.webContents.openDevTools(); + // tab devtools + view.webContents.openDevTools(); view.setAutoResize({width: true, height: true}); electron.ipcRenderer.send('tab-added', view.webContents.id); diff --git a/electron/common.ts b/electron/common.ts index 82f0c3d..6198805 100644 --- a/electron/common.ts +++ b/electron/common.ts @@ -2,7 +2,7 @@ import * as electron from 'electron'; import * as fs from 'fs'; import * as path from 'path'; -export const defaultHost = 'wss://chat.f-list.net:9799'; +export const defaultHost = 'wss://chat.f-list.net/chat2'; export class GeneralSettings { account = ''; diff --git a/electron/pack.js b/electron/pack.js index e33eaa4..3e3f239 100644 --- a/electron/pack.js +++ b/electron/pack.js @@ -3,6 +3,7 @@ const path = require('path'); const pkg = require(path.join(__dirname, 'package.json')); const fs = require('fs'); const child_process = require('child_process'); +const _ = require('lodash'); function mkdir(dir) { try { @@ -30,12 +31,33 @@ function mkdir(dir) { const distDir = path.join(__dirname, 'dist'); const isBeta = pkg.version.indexOf('beta') !== -1; -const spellcheckerPath = 'spellchecker/build/Release/spellchecker.node', keytarPath = 'keytar/build/Release/keytar.node'; const modules = path.join(__dirname, 'app', 'node_modules'); -mkdir(path.dirname(path.join(modules, spellcheckerPath))); -mkdir(path.dirname(path.join(modules, keytarPath))); -fs.copyFileSync(require.resolve(spellcheckerPath), path.join(modules, spellcheckerPath)); -fs.copyFileSync(require.resolve(keytarPath), path.join(modules, keytarPath)); + +// const spellcheckerPath = 'spellchecker/build/Release/spellchecker.node', +// keytarPath = 'keytar/build/Release/keytar.node', +// integerPath = 'integer/build/Release/integer.node', +// betterSqlite3 = 'better-sqlite3/build/Release/better_sqlite3.node'; +// +// mkdir(path.dirname(path.join(modules, spellcheckerPath))); +// mkdir(path.dirname(path.join(modules, keytarPath))); +// fs.copyFileSync(require.resolve(spellcheckerPath), path.join(modules, spellcheckerPath)); +// fs.copyFileSync(require.resolve(keytarPath), path.join(modules, keytarPath)); + +const includedPaths = [ + 'spellchecker/build/Release/spellchecker.node', + 'keytar/build/Release/keytar.node', + 'integer/build/Release/integer.node', + 'better-sqlite3/build/Release/better_sqlite3.node' +]; + +_.each( + includedPaths, + (p) => { + mkdir(path.dirname(path.join(modules, p))); + fs.copyFileSync(require.resolve(p), path.join(modules, p)); + } +); + require('electron-packager')({ diff --git a/electron/package.json b/electron/package.json index 47fd54c..e0eafc2 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "fchat", - "version": "3.0.10-horse", + "version": "3.0.10-ascending-v1", "author": "The F-List Team", "description": "F-List.net Chat Client", "main": "main.js", diff --git a/learn/cache-manager.ts b/learn/cache-manager.ts index cacb7c6..b17daee 100644 --- a/learn/cache-manager.ts +++ b/learn/cache-manager.ts @@ -4,11 +4,12 @@ import { ChannelAdEvent, ChannelMessageEvent, CharacterDataEvent, EventBus } fro import { Conversation } from '../chat/interfaces'; import { methods } from '../site/character_page/data_store'; import { Character } from '../site/character_page/interfaces'; -import { Gender } from '../site/character_page/matcher'; +import { Gender } from './matcher'; import { AdCache } from './ad-cache'; import { ChannelConversationCache } from './channel-conversation-cache'; import { CharacterProfiler } from './character-profiler'; import { ProfileCache } from './profile-cache'; +import { SqliteStore } from './sqlite-store'; import Timer = NodeJS.Timer; import ChannelConversation = Conversation.ChannelConversation; import Message = Conversation.Message; @@ -23,7 +24,7 @@ export interface ProfileCacheQueueEntry { export class CacheManager { - static readonly PROFILE_QUERY_DELAY = 3000; //1 * 1000; + static readonly PROFILE_QUERY_DELAY = 1 * 1000; //1 * 1000; adCache: AdCache = new AdCache(); profileCache: ProfileCache = new ProfileCache(); @@ -34,13 +35,15 @@ export class CacheManager { protected profileTimer: Timer | null = null; protected characterProfiler: CharacterProfiler | undefined; + protected profileStore = new SqliteStore(); + queueForFetching(name: string): void { - const key = ProfileCache.nameKey(name); - - if (this.profileCache.has(key)) + if (this.profileCache.get(name)) return; + const key = ProfileCache.nameKey(name); + if (!!_.find(this.queue, (q: ProfileCacheQueueEntry) => (q.key === key))) return; @@ -132,6 +135,9 @@ export class CacheManager { start(): void { this.stop(); + this.profileStore.start(); + this.profileCache.setStore(this.profileStore); + EventBus.$on( 'character-data', (data: CharacterDataEvent) => { @@ -189,8 +195,14 @@ export class CacheManager { const next = this.consumeNextInQueue(); if (next) { - // console.log('Learn fetch', next.name, next.score); - await this.fetchProfile(next.name); + try { + // console.log('Learn fetch', next.name, next.score); + await this.fetchProfile(next.name); + } catch (err) { + console.error('Profile queue error', err); + + this.queue.push(next); // return to queue + } } scheduleNextFetch(); @@ -208,6 +220,8 @@ export class CacheManager { this.profileTimer = null; } + this.profileStore.stop(); + // should do some $off here } diff --git a/learn/character-profiler.ts b/learn/character-profiler.ts index 9f9f63a..11b0cf5 100644 --- a/learn/character-profiler.ts +++ b/learn/character-profiler.ts @@ -1,7 +1,7 @@ import core from '../chat/core'; import { Character as CharacterFChatInf } from '../fchat'; -import { Character } from '../site/character_page/interfaces'; -import { Matcher } from '../site/character_page/matcher'; +import { Character as ComplexCharacter } from '../site/character_page/interfaces'; +import { Matcher, TagId } from './matcher'; import { AdCache } from './ad-cache'; import { ProfileCacheQueueEntry } from './cache-manager'; @@ -11,9 +11,9 @@ export class CharacterProfiler { static readonly ADVERTISEMENT_POTENTIAL_RAGE = 50 * 60 * 1000; protected adCache: AdCache; - protected me: Character; + protected me: ComplexCharacter; - constructor(me: Character, adCache: AdCache) { + constructor(me: ComplexCharacter, adCache: AdCache) { this.me = me; this.adCache = adCache; } @@ -48,14 +48,16 @@ export class CharacterProfiler { } - getInterestScoreForGender(me: Character, c: CharacterFChatInf.Character): number { + getInterestScoreForGender(me: ComplexCharacter, c: CharacterFChatInf.Character): number { const g = Matcher.strToGender(c.gender); if (g === null) { return 0; } - const score = Matcher.scoreOrientationByGender(me.character, g); + const myGender = Matcher.getTagValueList(TagId.Gender, me.character); + const myOrientation = Matcher.getTagValueList(TagId.Orientation, me.character); + const score = Matcher.scoreOrientationByGender(myGender, myOrientation, g); return score.score; } diff --git a/site/character_page/matcher.ts b/learn/matcher.ts similarity index 87% rename from site/character_page/matcher.ts rename to learn/matcher.ts index 518068b..45165ee 100644 --- a/site/character_page/matcher.ts +++ b/learn/matcher.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { Character, CharacterInfotag } from '../../interfaces'; +import { Character, CharacterInfotag } from '../interfaces'; /* eslint-disable no-null-keyword */ @@ -79,13 +79,12 @@ enum Kink { Mammals = 224 } -enum FurryPreference { +export enum FurryPreference { FurriesOnly = 39, FursAndHumans = 40, HumansOnly = 41, HumansPreferredFurriesOk = 150, FurriesPreferredHumansOk = 149 - } interface GenderKinkIdMap { @@ -105,7 +104,7 @@ const genderKinkMapping: GenderKinkIdMap = { // if no species and 'no furry chareacters', === human // if no species and dislike 'antho characters' === human -enum Species { +export enum Species { Human = 609, Equine = 236, Feline = 212, @@ -230,7 +229,10 @@ export interface MatchResult { them: Character, scores: MatchResultScores; info: MatchResultCharacterInfo; - total: number + total: number; + + yourAnalysis: CharacterAnalysis; + theirAnalysis: CharacterAnalysis; } export enum Scoring { @@ -274,6 +276,39 @@ export class Score { } } + +export class CharacterAnalysis { + readonly character: Character; + + readonly gender: Gender | null; + readonly orientation: Orientation | null; + readonly species: Species | null; + readonly furryPreference: FurryPreference | null; + readonly age: number | null; + + readonly isAnthro: boolean | null; + readonly isHuman: boolean | null; + readonly isMammal: boolean | null; + + constructor(c: Character) { + this.character = c; + + this.gender = Matcher.getTagValueList(TagId.Gender, c); + this.orientation = Matcher.getTagValueList(TagId.Orientation, c); + this.species = Matcher.species(c); + this.furryPreference = Matcher.getTagValueList(TagId.FurryPreference, c); + + const ageTag = Matcher.getTagValue(TagId.Age, c); + + this.age = ((ageTag) && (ageTag.string)) ? parseInt(ageTag.string, 10) : null; + + this.isAnthro = Matcher.isAnthro(c); + this.isHuman = Matcher.isHuman(c); + this.isMammal = Matcher.isMammal(c); + } +} + + /** * Answers the question: What YOU think about THEM * Never what THEY think about YOU @@ -282,17 +317,27 @@ export class Score { * to get the full picture */ export class Matcher { - you: Character; - them: Character; + readonly you: Character; + readonly them: Character; - constructor(you: Character, them: Character) { + readonly yourAnalysis: CharacterAnalysis; + readonly theirAnalysis: CharacterAnalysis; + + + constructor(you: Character, them: Character, yourAnalysis?: CharacterAnalysis, theirAnalysis?: CharacterAnalysis) { this.you = you; this.them = them; + + this.yourAnalysis = yourAnalysis || new CharacterAnalysis(you); + this.theirAnalysis = theirAnalysis || new CharacterAnalysis(them); } static generateReport(you: Character, them: Character): MatchReport { - const youThem = new Matcher(you, them); - const themYou = new Matcher(them, you); + const yourAnalysis = new CharacterAnalysis(you); + const theirAnalysis = new CharacterAnalysis(them); + + const youThem = new Matcher(you, them, yourAnalysis, theirAnalysis); + const themYou = new Matcher(them, you, theirAnalysis, yourAnalysis); return { you: youThem.match(), @@ -300,11 +345,14 @@ export class Matcher { }; } - match(): MatchResult { - const data = { + const data: MatchResult = { you: this.you, them: this.them, + + yourAnalysis: this.yourAnalysis, + theirAnalysis: this.theirAnalysis, + total: 0, scores: { @@ -332,29 +380,19 @@ export class Matcher { } private resolveOrientationScore(): Score { - const you = this.you; - const them = this.them; - - const yourGender = Matcher.getTagValueList(TagId.Gender, you); - const theirGender = Matcher.getTagValueList(TagId.Gender, them); - const yourOrientation = Matcher.getTagValueList(TagId.Orientation, you); - - if ((yourGender === null) || (theirGender === null) || (yourOrientation === null)) - return new Score(Scoring.NEUTRAL); - // Question: If someone identifies themselves as 'straight cuntboy', how should they be matched? like a straight female? - return Matcher.scoreOrientationByGender(you, theirGender); + return Matcher.scoreOrientationByGender(this.yourAnalysis.gender, this.yourAnalysis.orientation, this.theirAnalysis.gender); } - static scoreOrientationByGender(you: Character, theirGender: Gender): Score { - const yourGender = Matcher.getTagValueList(TagId.Gender, you); - const yourOrientation = Matcher.getTagValueList(TagId.Orientation, you); + static scoreOrientationByGender(yourGender: Gender | null, yourOrientation: Orientation | null, theirGender: Gender | null): Score { + if ((yourGender === null) || (theirGender === null) || (yourOrientation === null)) + return new Score(Scoring.NEUTRAL); // CIS // tslint:disable-next-line curly - if ((yourGender !== null) && (Matcher.isCisGender(yourGender))) { + if (Matcher.isCisGender(yourGender)) { if (yourGender === theirGender) { // same sex CIS if (yourOrientation === Orientation.Straight) @@ -420,8 +458,8 @@ export class Matcher { private resolveSpeciesScore(): Score { const you = this.you; - const them = this.them; - const theirSpecies = Matcher.species(them); + const theirAnalysis = this.theirAnalysis; + const theirSpecies = theirAnalysis.species; if (theirSpecies === null) return new Score(Scoring.NEUTRAL); @@ -434,14 +472,14 @@ export class Matcher { return Matcher.formatKinkScore(speciesScore, speciesName); } - if (Matcher.isAnthro(them)) { + if (theirAnalysis.isAnthro) { const anthroScore = Matcher.getKinkPreference(you, Kink.AnthroCharacters); if (anthroScore !== null) return Matcher.formatKinkScore(anthroScore, 'anthros'); } - if (Matcher.isMammal(them)) { + if (theirAnalysis.isMammal) { const mammalScore = Matcher.getKinkPreference(you, Kink.Mammals); if (mammalScore !== null) @@ -451,6 +489,7 @@ export class Matcher { return new Score(Scoring.NEUTRAL); } + formatScoring(score: Scoring, description: string): Score { let type = ''; @@ -477,10 +516,8 @@ export class Matcher { private resolveFurryPairingsScore(): Score { const you = this.you; - const them = this.them; - - const theyAreAnthro = Matcher.isAnthro(them); - const theyAreHuman = Matcher.isHuman(them); + const theyAreAnthro = this.theirAnalysis.isAnthro; + const theyAreHuman = this.theirAnalysis.isHuman; const score = theyAreAnthro ? Matcher.furryLikeabilityScore(you) @@ -537,19 +574,11 @@ export class Matcher { private resolveAgeScore(): Score { const you = this.you; - const them = this.them; + const theirAge = this.theirAnalysis.age; - const yourAgeTag = Matcher.getTagValue(TagId.Age, you); - const theirAgeTag = Matcher.getTagValue(TagId.Age, them); - - if (!theirAgeTag) + if (theirAge === null) return new Score(Scoring.NEUTRAL); - if (!theirAgeTag.string) - return new Score(Scoring.NEUTRAL); - - const theirAge = parseInt(theirAgeTag.string, 10); - const ageplayScore = Matcher.getKinkPreference(you, Kink.Ageplay); const underageScore = Matcher.getKinkPreference(you, Kink.UnderageCharacters); @@ -562,12 +591,12 @@ export class Matcher { if ((theirAge < 18) && (underageScore !== null)) return Matcher.formatKinkScore(underageScore, `ages of ${theirAge}`); - if ((yourAgeTag) && (yourAgeTag.string)) { + const yourAge = this.yourAnalysis.age; + + if (yourAge !== null) { const olderCharactersScore = Matcher.getKinkPreference(you, Kink.OlderCharacters); const youngerCharactersScore = Matcher.getKinkPreference(you, Kink.YoungerCharacters); - const yourAge = parseInt(yourAgeTag.string, 10); - if ((yourAge < theirAge) && (olderCharactersScore !== null)) return Matcher.formatKinkScore(olderCharactersScore, 'older characters'); @@ -580,9 +609,8 @@ export class Matcher { private resolveGenderScore(): Score { const you = this.you; - const them = this.them; - const theirGender = Matcher.getTagValueList(TagId.Gender, them); + const theirGender = this.theirAnalysis.gender; if (theirGender === null) return new Score(Scoring.NEUTRAL); @@ -609,7 +637,7 @@ export class Matcher { return t.list; } - static isCisGender(...genders: Gender[]): boolean { + static isCisGender(...genders: Gender[] | null[]): boolean { return _.every(genders, (g: Gender) => ((g === Gender.Female) || (g === Gender.Male))); } diff --git a/learn/profile-cache.ts b/learn/profile-cache.ts index 1a0655b..d74129c 100644 --- a/learn/profile-cache.ts +++ b/learn/profile-cache.ts @@ -2,8 +2,9 @@ import * as _ from 'lodash'; import core from '../chat/core'; import { Character } from '../site/character_page/interfaces'; -import { Matcher, Score, Scoring } from '../site/character_page/matcher'; +import { Matcher, Score, Scoring } from './matcher'; import { Cache } from './cache'; +import { SqliteStore } from './sqlite-store'; export interface CharacterCacheRecord { character: Character; @@ -13,10 +14,39 @@ export interface CharacterCacheRecord { } export class ProfileCache extends Cache { - register(c: Character): CharacterCacheRecord { + protected store?: SqliteStore; + + + setStore(store: SqliteStore): void { + this.store = store; + } + + + get(name: string, skipStore: boolean = false): CharacterCacheRecord | null { + const v = super.get(name); + + if ((v !== null) || (!this.store) || (skipStore)) { + return v; + } + + const pd = this.store.getProfile(name); + + if (!pd) { + return null; + } + + return this.register(pd.profileData, true); + } + + + register(c: Character, skipStore: boolean = false): CharacterCacheRecord { const k = Cache.nameKey(c.character.name); const score = ProfileCache.score(c); + if ((this.store) && (!skipStore)) { + this.store.storeProfile(c); + } + if (k in this.cache) { const rExisting = this.cache[k]; diff --git a/learn/sqlite-store.ts b/learn/sqlite-store.ts new file mode 100644 index 0000000..020ed28 --- /dev/null +++ b/learn/sqlite-store.ts @@ -0,0 +1,187 @@ +// import * as Sqlite from 'better-sqlite3'; +// tslint:disable-next-line:no-duplicate-imports +import * as path from 'path'; + +import { Database, Statement } from 'better-sqlite3'; +import core from '../chat/core'; + +// tslint:disable-next-line: no-require-imports +// const Sqlite = require('better-sqlite3'); +import { nativeRequire } from '../electron/common'; + +// tslint:disable-next-line: no-any +const Sqlite = nativeRequire('better-sqlite3'); + + +import { Orientation, Gender, FurryPreference, Species, CharacterAnalysis } from './matcher'; +import { Character as ComplexCharacter } from '../site/character_page/interfaces'; + +export interface ProfileRecord { + id: string; + name: string; + profileData: ComplexCharacter; + firstSeen: number; + lastFetched: number; + gender: Gender | null; + orientation: Orientation | null; + furryPreference: FurryPreference | null; + species: Species | null; + age: number | null; + domSubRole: number | null; + position: number | null; + lastCounted: number | null; + guestbookCount: number | null; + friendCount: number | null; + groupCount: number | null; +} + +// export type Statement = any; +// export type Database = any; + + +export class SqliteStore { + + protected stmtGetProfile: Statement; + protected stmtStoreProfile: Statement; + protected stmtUpdateCounts: Statement; + + protected db: Database; + protected checkpointTimer: NodeJS.Timer | null = null; + + constructor() { + const dbFile = path.join(core.state.generalSettings!.logDirectory, 'fchat-ascending.sqlite'); + + // tslint:disable-next-line: no-unsafe-any + this.db = new Sqlite(dbFile, {}); + + this.init(); + this.migrateDatabase(); + + this.stmtGetProfile = this.db.prepare('SELECT * FROM profiles WHERE id = ?'); + + this.stmtStoreProfile = this.db.prepare( + `INSERT INTO profiles + (id, name, profileData, firstSeen, lastFetched, gender, orientation, furryPreference, + species, age, domSubRole, position) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + profileData=excluded.profileData, + lastFetched=excluded.lastFetched, + gender=excluded.gender, + orientation=excluded.orientation, + furryPreference=excluded.furryPreference, + species=excluded.species, + age=excluded.age, + domSubRole=excluded.domSubRole, + position=excluded.position + `); + + this.stmtUpdateCounts = this.db.prepare( + 'UPDATE profiles SET lastCounted = ?, guestbookCount = ?, friendCount = ?, groupCount = ? WHERE id = ? LIMIT 1' + ); + } + + + // tslint:disable-next-line: prefer-function-over-method + protected toProfileId(name: string): string { + return name.toLowerCase(); + } + + + getProfile(name: string): ProfileRecord | undefined { + const data = this.stmtGetProfile.get(this.toProfileId(name)); + + if (!data) { + return; + } + + // tslint:disable-next-line: no-unsafe-any + data.profileData = JSON.parse(data.profileData) as ComplexCharacter; + + return data as ProfileRecord; + } + + + storeProfile(c: ComplexCharacter): void { + const ca = new CharacterAnalysis(c.character); + + const data = [ + this.toProfileId(c.character.name), + c.character.name, + JSON.stringify(c), + Math.round(Date.now() / 1000), + Math.round(Date.now() / 1000), + ca.gender, + ca.orientation, + ca.furryPreference, + ca.species, + ca.age, + null, // domSubRole + null // position + ]; + + this.stmtStoreProfile.run(data); + } + + + updateProfileCounts(name: string, guestbookCount: number | null, friendCount: number | null, groupCount: number | null): void { + this.stmtUpdateCounts.run(Math.round(Date.now() / 1000), guestbookCount, friendCount, groupCount, this.toProfileId(name)); + } + + + protected init(): void { + this.db.pragma('journal_mode = WAL'); + } + + + protected migrateDatabase(): void { + this.db.exec( + `CREATE TABLE IF NOT EXISTS "migration" ( + "version" INTEGER NOT NULL + , UNIQUE("version") + ); + + CREATE TABLE IF NOT EXISTS "profiles" ( + "id" TEXT NOT NULL PRIMARY KEY + , "name" TEXT NOT NULL + , "profileData" TEXT NOT NULL + , "firstSeen" INTEGER NOT NULL + , "lastFetched" INTEGER NOT NULL + , "lastCounted" INTEGER + , "gender" INTEGER + , "orientation" INTEGER + , "furryPreference" INTEGER + , "species" INTEGER + , "age" INTEGER + , "domSubRole" INTEGER + , "position" INTEGER + , "guestbookCount" INTEGER + , "friendCount" INTEGER + , "groupCount" INTEGER + , UNIQUE("id") + ); + + INSERT OR IGNORE INTO migration(version) VALUES(1); + `); + } + + + start(): void { + this.stop(); + + this.checkpointTimer = setInterval( + () => this.db.checkpoint(), + 10 * 60 * 1000 + ); + } + + + stop(): void { + if (this.checkpointTimer) { + clearInterval(this.checkpointTimer); + + this.checkpointTimer = null; + } + } +} + diff --git a/package-lock.json b/package-lock.json index b8009a9..93e04e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,21 @@ "integrity": "sha512-0afQfB+HBeJHlXPzcF2Jjh78SbwPSkDjba/O7pZFzAW3WGKNzd4s4AqrZo7oIlMWGnfoyDo8+QeosK0+DTDrTg==", "dev": true }, + "@types/better-sqlite3": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-5.4.0.tgz", + "integrity": "sha512-nzm7lJ7l3jBmGUbtkL8cdOMhPkN6Pw2IM+b0V7iIKba+YKiLrjkIy7vVLsBIVnd7+lgzBzrHsXZxCaFTcmw5Ow==", + "dev": true, + "requires": { + "@types/integer": "*" + } + }, + "@types/integer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/integer/-/integer-1.0.0.tgz", + "integrity": "sha512-3viiRKLoSP2Qr78nMoQjkDc0fan4BgmpOyV1+1gKjE8wWXo3QQ78WItO6f9WuBf3qe3ymDYhM65oqHTOZ0rFxw==", + "dev": true + }, "@types/lodash": { "version": "4.14.119", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz", @@ -873,6 +888,31 @@ "callsite": "1.0.0" } }, + "better-sqlite3": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-5.4.0.tgz", + "integrity": "sha512-Uj1ZYOcq1GtFyFgJgqMVDoDLTy1B1pM9+bULnlX8szRX4cPjE/7JbKxCzQGhYlZlLkHQvtXXhCZ3skqsQ2byMA==", + "requires": { + "integer": "^2.1.0", + "tar": "^4.4.6" + }, + "dependencies": { + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + } + } + }, "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", @@ -1409,9 +1449,9 @@ } }, "cli-spinners": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", - "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", "dev": true }, "cliui": { @@ -1425,6 +1465,12 @@ "wrap-ansi": "^2.0.0" } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, "clone-deep": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", @@ -2091,6 +2137,15 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2509,42 +2564,332 @@ } }, "electron-rebuild": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.8.2.tgz", - "integrity": "sha512-EeR4dgb6NN7ybxduUWMeeLhU/EuF+FzwFZJfMJXD0bx96K+ttAieCXOn9lTO5nA9Qn3hiS7pEpk8pZ9StpGgSg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.8.5.tgz", + "integrity": "sha512-gDwRA3utfiPnFwBZ1z8M4SEMwsdsy6Bg4VGO2ohelMOIO0vxiCrDQ/FVdLk3h2g7fLb06QFUsQU+86jiTSmZxw==", "dev": true, "requires": { - "colors": "^1.2.0", - "debug": "^2.6.3", + "colors": "^1.3.3", + "debug": "^4.1.1", "detect-libc": "^1.0.3", - "fs-extra": "^3.0.1", - "node-abi": "^2.0.0", - "node-gyp": "^3.6.0", - "ora": "^1.2.0", - "rimraf": "^2.6.1", - "spawn-rx": "^2.0.10", - "yargs": "^7.0.2" + "fs-extra": "^7.0.1", + "node-abi": "^2.8.0", + "node-gyp": "^4.0.0", + "ora": "^3.4.0", + "spawn-rx": "^3.0.0", + "yargs": "^13.2.2" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "jsonfile": "^3.0.0", + "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { "graceful-fs": "^4.1.6" } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-abi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz", + "integrity": "sha512-jmEOvv0eanWjhX8dX1pmjb7oJl1U1oR4FOh0b2GnvALwSYoOdU7sj+kLDSAyjo4pfC9aj/IxkloxdLJQhSSQBA==", + "dev": true, + "requires": { + "semver": "^5.4.1" + } + }, + "node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", + "dev": true, + "requires": { + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^4.4.8", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -2604,6 +2949,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -3334,6 +3685,14 @@ } } }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "requires": { + "minipass": "^2.2.1" + } + }, "fs-temp": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fs-temp/-/fs-temp-1.1.2.tgz", @@ -4097,6 +4456,27 @@ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -4488,6 +4868,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "integer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/integer/-/integer-2.1.0.tgz", + "integrity": "sha512-vBtiSgrEiNocWvvZX1RVfeOKa2mCHLZQ2p9nkQkQZ/BvEiY+6CcUz0eyjvIiewjJoeNidzg2I+tpPJvpyspL1w==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -4749,8 +5134,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "optional": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-svg": { "version": "3.0.0", @@ -5132,6 +5516,15 @@ } } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -5176,6 +5569,25 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } + } + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -5311,6 +5723,23 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -5504,6 +5933,12 @@ "inherits": "~2.0.1" } }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node-abi": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.5.0.tgz", @@ -5853,15 +6288,45 @@ } }, "ora": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", - "integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", "dev": true, "requires": { - "chalk": "^2.1.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", - "cli-spinners": "^1.0.1", - "log-symbols": "^2.1.0" + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "os-browserify": { @@ -5899,6 +6364,24 @@ "os-tmpdir": "^1.0.0" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -7095,12 +7578,12 @@ } }, "rxjs": { - "version": "5.5.12", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", - "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -7644,14 +8127,14 @@ "dev": true }, "spawn-rx": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz", - "integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-3.0.0.tgz", + "integrity": "sha512-dw4Ryg/KMNfkKa5ezAR5aZe9wNwPdKlnHEXtHOjVnyEDSPQyOpIPPRtcIiu7127SmtHhaCjw21yC43HliW0iIg==", "dev": true, "requires": { "debug": "^2.5.1", "lodash.assign": "^4.2.0", - "rxjs": "^5.1.1" + "rxjs": "^6.3.1" } }, "spdx-correct": { @@ -7866,8 +8349,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "optional": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { "version": "1.0.1", @@ -7944,12 +8426,6 @@ "util.promisify": "~1.0.0" } }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, "tapable": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", @@ -8622,6 +9098,15 @@ "neo-async": "^2.5.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webpack": { "version": "4.27.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.27.1.tgz", @@ -8768,8 +9253,7 @@ "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "7.1.0", diff --git a/package.json b/package.json index 3ad4a98..74bb70b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@fortawesome/fontawesome-free": "^5.6.1", "@types/lodash": "^4.14.119", "@types/sortablejs": "^1.7.0", + "@types/better-sqlite3": "^5.4.0", "@vue/devtools": "^5.1.0", "axios": "^0.18.0", "bootstrap": "^4.1.3", @@ -18,7 +19,7 @@ "electron": "3.0.13", "electron-log": "^2.2.17", "electron-packager": "^13.0.1", - "electron-rebuild": "^1.8.2", + "electron-rebuild": "^1.8.5", "extract-loader": "^3.1.0", "file-loader": "^2.0.0", "lodash": "^4.17.11", @@ -39,6 +40,7 @@ "webpack": "^4.27.1" }, "dependencies": { + "better-sqlite3": "^5.4.0", "keytar": "^4.3.0", "spellchecker": "^3.5.0" }, @@ -48,6 +50,6 @@ "electron-winstaller": "^2.7.0" }, "scripts": { - "postinstall": "electron-rebuild -o spellchecker,keytar" + "postinstall": "electron-rebuild -o spellchecker,keytar,better-sqlite3,integer" } } diff --git a/readme.md b/readme.md index d72809b..23946f4 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# F-Chat Rising +# F-Chat Ascending This repository contains a modified version of the mainline F-Chat 3.0 client. @@ -9,6 +9,8 @@ This repository contains a modified version of the mainline F-Chat 3.0 client. * Manage channel's ad settings via "Tab Settings" * Automatically re-post ads every 11-18 minutes (randomized) for up to 180 minutes * Rotate multiple ads on a single channel +* Ad Rating + * LFP ads are automatically rated and matched against your profile * Link previews * Hover cursor over any `[url]` to see a preview of it * Middle click any `[url]` to turn the preview into a sticky / interactive mode diff --git a/site/character_page/character_page.vue b/site/character_page/character_page.vue index ede50db..e208371 100644 --- a/site/character_page/character_page.vue +++ b/site/character_page/character_page.vue @@ -86,7 +86,7 @@ import CharacterKinksView from './kinks.vue'; import Sidebar from './sidebar.vue'; import core from '../../chat/core'; - import { Matcher, MatchReport } from './matcher'; + import { Matcher, MatchReport } from '../../learn/matcher'; import MatchReportView from './match-report.vue'; diff --git a/site/character_page/infotag.vue b/site/character_page/infotag.vue index b25e623..640c10c 100644 --- a/site/character_page/infotag.vue +++ b/site/character_page/infotag.vue @@ -13,7 +13,7 @@ import { DisplayInfotag } from './interfaces'; // import { Character as CharacterInfo } from '../../interfaces'; import {Store} from './data_store'; - import { MatchReport, TagId } from './matcher'; + import { MatchReport, TagId } from '../../learn/matcher'; import { CssClassMap } from './match-report.vue'; diff --git a/site/character_page/infotags.vue b/site/character_page/infotags.vue index 679c8df..13502ef 100644 --- a/site/character_page/infotags.vue +++ b/site/character_page/infotags.vue @@ -16,7 +16,7 @@ import {Character, CONTACT_GROUP_ID, DisplayInfotag} from './interfaces'; import InfotagView from './infotag.vue'; - import { MatchReport } from './matcher'; + import { MatchReport } from '../../learn/matcher'; interface DisplayInfotagGroup { id: number diff --git a/site/character_page/match-report.vue b/site/character_page/match-report.vue index 7eee193..645ed63 100644 --- a/site/character_page/match-report.vue +++ b/site/character_page/match-report.vue @@ -35,7 +35,7 @@ import * as _ from 'lodash'; import Vue from 'vue'; import * as Utils from '../utils'; - import { MatchReport, MatchResult, Score, Scoring } from './matcher'; + import { MatchReport, MatchResult, Score, Scoring } from '../../learn/matcher'; export interface CssClassMap { [key: string]: boolean; diff --git a/site/character_page/sidebar.vue b/site/character_page/sidebar.vue index 1e3f787..44041e4 100644 --- a/site/character_page/sidebar.vue +++ b/site/character_page/sidebar.vue @@ -101,7 +101,7 @@ import FriendDialog from './friend_dialog.vue'; import InfotagView from './infotag.vue'; import {Character, CONTACT_GROUP_ID, SharedStore} from './interfaces'; - import { MatchReport } from './matcher'; + import { MatchReport } from '../../learn/matcher'; import MemoDialog from './memo_dialog.vue'; import ReportDialog from './report_dialog.vue';