Better search rankings
This commit is contained in:
parent
e8c78079b1
commit
4ceee99520
|
@ -2,6 +2,9 @@
|
|||
|
||||
## Canary
|
||||
* Use `Ctrl+Tab`, `Ctrl+Shift+Tab`, `Ctrl+PgDown`, and `Ctrl+PgUp` to switch between character tabs
|
||||
* Better search rankings
|
||||
* Really good matches get 'unicorn' tag
|
||||
* Fixed IMGBB, Shadbase previews
|
||||
|
||||
|
||||
## 1.4.1
|
||||
|
|
|
@ -91,24 +91,34 @@
|
|||
const xc = core.cache.profileCache.getSync(x.name);
|
||||
const yc = core.cache.profileCache.getSync(y.name);
|
||||
|
||||
if (xc && !yc) {
|
||||
if(xc && !yc) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!xc && yc) {
|
||||
if(!xc && yc) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (xc && yc) {
|
||||
if (xc.matchScore > yc.matchScore)
|
||||
if(xc && yc) {
|
||||
if(xc.match.matchScore > yc.match.matchScore)
|
||||
return -1;
|
||||
|
||||
if (xc.matchScore < yc.matchScore)
|
||||
if(xc.match.matchScore < yc.match.matchScore)
|
||||
return 1;
|
||||
|
||||
if(xc.match.searchScore > yc.match.searchScore)
|
||||
return -1;
|
||||
|
||||
if(xc.match.searchScore < yc.match.searchScore)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(x.name < y.name) return -1;
|
||||
if(x.name > y.name) return 1;
|
||||
if(x.name < y.name)
|
||||
return -1;
|
||||
|
||||
if(x.name > y.name)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -777,11 +777,23 @@
|
|||
border-radius: 3px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 75%;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
line-height: 100%;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&.unicorn {
|
||||
background-color: #00adad;
|
||||
border: solid 1px #1d9a9a;
|
||||
box-shadow: 0 0 5px 0 rgba(255, 255, 255, 0.5);
|
||||
|
||||
&::before {
|
||||
content: '🦄';
|
||||
padding-right:3px
|
||||
}
|
||||
}
|
||||
|
||||
&.match {
|
||||
background-color: var(--scoreMatchBg);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { Component, Hook, Prop, Watch } from '@f-list/vue-ts';
|
||||
import Vue from 'vue';
|
||||
import {Channel, Character} from '../fchat';
|
||||
import { Score, Scoring } from '../learn/matcher';
|
||||
import { Matcher, Score, Scoring } from '../learn/matcher';
|
||||
import core from './core';
|
||||
import { EventBus } from './preview/event-bus';
|
||||
|
||||
|
@ -37,7 +37,7 @@ export interface StatusClasses {
|
|||
rankIcon: string | null;
|
||||
statusClass: string | null;
|
||||
matchClass: string | null;
|
||||
matchScore: number | null;
|
||||
matchScore: number | string | null;
|
||||
userClass: string;
|
||||
isBookmark: boolean;
|
||||
}
|
||||
|
@ -71,8 +71,13 @@ export function getStatusClasses(
|
|||
const cache = core.cache.profileCache.getSync(character.name);
|
||||
|
||||
if (cache) {
|
||||
matchClass = `match-found ${Score.getClasses(cache.matchScore)}`;
|
||||
matchScore = cache.matchScore;
|
||||
if ((cache.match.searchScore > Matcher.UNICORN_LEVEL) && (cache.match.matchScore === Scoring.MATCH)) {
|
||||
matchClass = 'match-found unicorn';
|
||||
matchScore = 'unicorn';
|
||||
} else {
|
||||
matchClass = `match-found ${Score.getClasses(cache.match.matchScore)}`;
|
||||
matchScore = cache.match.matchScore;
|
||||
}
|
||||
} else {
|
||||
/* tslint:disable-next-line no-floating-promises */
|
||||
core.cache.addProfile(character.name);
|
||||
|
@ -126,7 +131,7 @@ export default class UserView extends Vue {
|
|||
rankIcon: string | null = null;
|
||||
statusClass: string | null = null;
|
||||
matchClass: string | null = null;
|
||||
matchScore: number | null = null;
|
||||
matchScore: number | string | null = null;
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
scoreWatcher: ((event: any) => void) | null = null;
|
||||
|
@ -197,8 +202,11 @@ export default class UserView extends Vue {
|
|||
}
|
||||
|
||||
|
||||
getMatchScoreTitle(score: number | null): string {
|
||||
getMatchScoreTitle(score: number | string | null): string {
|
||||
switch (score) {
|
||||
case 'unicorn':
|
||||
return 'Unicorn';
|
||||
|
||||
case Scoring.MATCH:
|
||||
return 'Great';
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<modal :action="`Ads for ${conversation.name}`" @submit="submit" ref="dialog" @open="load()" dialogClass="w-100"
|
||||
:buttonText="l('conversationSettings.save')">
|
||||
|
||||
<div>
|
||||
[] Randomize the order of the ads every time you start automated posting.
|
||||
</div>
|
||||
|
||||
<div class="form-group ad-list" v-for="(ad, index) in ads">
|
||||
<label :for="'ad' + conversation.key + '-' + index" class="control-label">Ad #{{(index + 1)}}
|
||||
<a v-if="(index > 0)" @click="moveAdUp(index)" title="Move Up"><i class="fa fa-arrow-up"></i></a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="character-preview">
|
||||
<div v-if="match && character" class="row">
|
||||
<div class="col-2">
|
||||
|
@ -6,7 +6,10 @@
|
|||
</div>
|
||||
|
||||
<div class="col-10">
|
||||
<h1><span class="character-name" :class="(statusClasses || {}).userClass">{{ character.character.name }}</span></h1>
|
||||
<h1 class="user-view">
|
||||
<span class="character-name" :class="(statusClasses || {}).userClass">{{ character.character.name }}</span>
|
||||
<span v-if="((statusClasses) && (statusClasses.matchScore === 'unicorn'))" :class="(statusClasses || {}).matchClass">Unicorn</span>
|
||||
</h1>
|
||||
<h3>{{ getOnlineStatus() }}</h3>
|
||||
|
||||
<div class="summary">
|
||||
|
@ -23,6 +26,10 @@
|
|||
|
||||
<match-tags v-if="match" :match="match"></match-tags>
|
||||
|
||||
<!-- <div v-if="customs">-->
|
||||
<!-- <span v-for="c in customs" :class="Score.getClasses(c.score)">{{c.name}}</span>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="status-message" v-if="statusMessage">
|
||||
<h4>Status <span v-if="latestAd && (statusMessage === latestAd.message)">& Latest Ad</span></h4>
|
||||
<bbcode :text="statusMessage"></bbcode>
|
||||
|
@ -41,12 +48,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop } from '@f-list/vue-ts';
|
||||
import { Component, Hook, Prop } from '@f-list/vue-ts';
|
||||
import Vue from 'vue';
|
||||
import core from '../core';
|
||||
import { methods } from '../../site/character_page/data_store';
|
||||
import {Character as ComplexCharacter} from '../../site/character_page/interfaces';
|
||||
import { Matcher, MatchReport } from '../../learn/matcher';
|
||||
import { Matcher, MatchReport, Score } from '../../learn/matcher';
|
||||
import { Character as CharacterStatus } from '../../fchat';
|
||||
import { getStatusClasses, StatusClasses } from '../UserView.vue';
|
||||
import * as _ from 'lodash';
|
||||
|
@ -56,13 +63,19 @@ import * as Utils from '../../site/utils';
|
|||
import MatchTags from './MatchTags.vue';
|
||||
import {
|
||||
furryPreferenceMapping,
|
||||
Gender,
|
||||
Gender, kinkMapping,
|
||||
Orientation,
|
||||
Species,
|
||||
SubDomRole,
|
||||
TagId
|
||||
} from '../../learn/matcher-types';
|
||||
import { BBCodeView } from '../../bbcode/view';
|
||||
import { EventBus } from './event-bus';
|
||||
import { Character, CustomKink } from '../../interfaces';
|
||||
|
||||
interface CustomKinkWithScore extends CustomKink {
|
||||
score: number;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -93,11 +106,53 @@ export default class CharacterPreview extends Vue {
|
|||
|
||||
formatTime = formatTime;
|
||||
readonly avatarUrl = Utils.avatarURL;
|
||||
TagId = TagId;
|
||||
|
||||
async load(characterName: string): Promise<void> {
|
||||
TagId = TagId;
|
||||
Score = Score;
|
||||
|
||||
scoreWatcher: ((event: any) => void) | null = null;
|
||||
customs?: CustomKinkWithScore[];
|
||||
|
||||
|
||||
@Hook('mounted')
|
||||
mounted(): void {
|
||||
// tslint:disable-next-line no-unsafe-any no-any
|
||||
this.scoreWatcher = async(event: {character: Character, score: number}): Promise<void> => {
|
||||
// console.log('scoreWatcher', event);
|
||||
|
||||
if (
|
||||
(event.character)
|
||||
&& (this.characterName)
|
||||
&& (event.character.name === this.characterName)
|
||||
) {
|
||||
await this.load(this.characterName, true);
|
||||
}
|
||||
};
|
||||
|
||||
EventBus.$on(
|
||||
'character-score',
|
||||
this.scoreWatcher
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Hook('beforeDestroy')
|
||||
beforeDestroy(): void {
|
||||
if (this.scoreWatcher) {
|
||||
EventBus.$off(
|
||||
'character-score',
|
||||
this.scoreWatcher
|
||||
);
|
||||
|
||||
this.scoreWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async load(characterName: string, force: boolean = false): Promise<void> {
|
||||
if (
|
||||
(this.characterName === characterName)
|
||||
&& (!force)
|
||||
&& (this.match)
|
||||
&& (this.character)
|
||||
&& (this.ownCharacter)
|
||||
|
@ -112,6 +167,7 @@ export default class CharacterPreview extends Vue {
|
|||
|
||||
this.match = undefined;
|
||||
this.character = undefined;
|
||||
this.customs = undefined;
|
||||
this.ownCharacter = core.characters.ownProfile;
|
||||
|
||||
this.updateOnlineStatus();
|
||||
|
@ -120,6 +176,7 @@ export default class CharacterPreview extends Vue {
|
|||
this.character = await this.getCharacterData(characterName);
|
||||
this.match = Matcher.identifyBestMatchReport(this.ownCharacter.character, this.character.character);
|
||||
|
||||
this.updateCustoms();
|
||||
this.updateDetails();
|
||||
}
|
||||
|
||||
|
@ -133,10 +190,9 @@ export default class CharacterPreview extends Vue {
|
|||
}
|
||||
|
||||
this.statusMessage = this.onlineCharacter.statusText;
|
||||
this.statusClasses = getStatusClasses(this.onlineCharacter, undefined, true, false, false);
|
||||
this.statusClasses = getStatusClasses(this.onlineCharacter, undefined, true, false, true);
|
||||
}
|
||||
|
||||
|
||||
updateAdStatus(): void {
|
||||
const cache = core.cache.adCache.get(this.characterName!);
|
||||
|
||||
|
@ -149,6 +205,25 @@ export default class CharacterPreview extends Vue {
|
|||
}
|
||||
|
||||
|
||||
updateCustoms(): void {
|
||||
this.customs = _.orderBy(
|
||||
_.map(
|
||||
_.reject(this.character!.character.customs || [], (c) => _.isUndefined(c)) as CustomKink[],
|
||||
(c: CustomKink) => _.assign(
|
||||
{},
|
||||
c,
|
||||
{
|
||||
score: kinkMapping[c.choice],
|
||||
name: c.name.trim().replace(/^\W+/, '').replace(/\W+$/, '')
|
||||
}
|
||||
)
|
||||
),
|
||||
['score', 'name'],
|
||||
['desc', 'asc']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
updateDetails(): void {
|
||||
if (!this.match) {
|
||||
this.age = undefined;
|
||||
|
@ -240,6 +315,10 @@ export default class CharacterPreview extends Vue {
|
|||
border-radius: 0 5px 5px 5px;
|
||||
border: 1px solid var(--secondary);
|
||||
|
||||
.unicorn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 125%;
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ class FListImagePreviewDomMutator {
|
|||
}
|
||||
|
||||
const removeList = [];
|
||||
const safeIds = ['flistWrapper', 'flistError', 'flistHider'];
|
||||
const safeIds = ['flistWrapper', 'flistError', 'flistHider', 'flistStyle'];
|
||||
const safeTags = this.safeTags;
|
||||
|
||||
for (const el of body.childNodes) {
|
||||
|
@ -277,7 +277,17 @@ class FListImagePreviewDomMutator {
|
|||
|
||||
const el = document.createElement('style');
|
||||
|
||||
el.id = 'flistStyle';
|
||||
|
||||
el.textContent = `
|
||||
html {
|
||||
${this.getWrapperStyleOverrides()}
|
||||
}
|
||||
|
||||
body {
|
||||
${this.getWrapperStyleOverrides()}
|
||||
}
|
||||
|
||||
#flistWrapper img, #flistWrapper video {
|
||||
${this.getImageStyleOverrides()}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,7 @@ export class ImageDomMutator {
|
|||
this.add('furaffinity.net', this.getBaseJsMutatorScript(['#submissionImg', 'video', 'img']));
|
||||
this.add('rule34.paheal.net', this.getBaseJsMutatorScript(['#main_image', 'video', 'img']));
|
||||
this.add('xhamster.com', this.getBaseJsMutatorScript(['#photo_slider video', '#photo_slider img', 'video', 'img']));
|
||||
this.add('shadbase.com', this.getBaseJsMutatorScript(['#comic video', '#comic img', 'video', 'img']));
|
||||
|
||||
this.add(
|
||||
'pornhub.com',
|
||||
|
|
|
@ -97,5 +97,7 @@
|
|||
|
||||
[url=https://xhamster.com/videos/letsdoeit-check-out-the-sexiest-massage-sex-compilation-now-xh7hyIG]xHamster Video[/url]
|
||||
|
||||
[url=https://ibb.co/jMcYcPx]Imgbb[/url]
|
||||
|
||||
Broken
|
||||
https://vimeo.com/265884960
|
||||
|
|
|
@ -66,7 +66,7 @@ export class CacheManager {
|
|||
const c = await this.profileCache.get(name);
|
||||
|
||||
if (c) {
|
||||
this.updateAdScoringForProfile(c.character, c.matchScore);
|
||||
this.updateAdScoringForProfile(c.character, c.match.matchScore);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export class CacheManager {
|
|||
|
||||
const r = await this.profileCache.register(c);
|
||||
|
||||
this.updateAdScoringForProfile(c, r.matchScore);
|
||||
this.updateAdScoringForProfile(c, r.match.matchScore);
|
||||
|
||||
return c;
|
||||
} catch (err) {
|
||||
|
@ -395,10 +395,10 @@ export class CacheManager {
|
|||
// console.log(`Re-scored character ${char.name} to ${p.matchScore}`);
|
||||
// }
|
||||
|
||||
msg.score = p.matchScore;
|
||||
msg.score = p.match.matchScore;
|
||||
|
||||
if (populateAll) {
|
||||
this.populateAllConversationsWithScore(char.name, p.matchScore);
|
||||
this.populateAllConversationsWithScore(char.name, p.match.matchScore);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,7 @@ export const speciesMapping: SpeciesMap = {
|
|||
gen('(beast|anthro|furry)')
|
||||
],
|
||||
|
||||
[Species.Human]: ['human', 'homo sapiens', 'human.*', 'homo[ -]?sapi[ea]ns?', 'woman', 'h[uo]+m[aie]n', 'humaine?',
|
||||
[Species.Human]: ['human', 'homo sapiens', 'human.*', 'homo[ -]?sapi[ea]ns?', 'woman', 'hy?[uo]+m[aie]n', 'humaine?',
|
||||
'meat[ -]?popsicle',
|
||||
// where should these go?
|
||||
'angel', 'neph[ai]l[ei]m', 'arch[ -]?angel'
|
||||
|
@ -257,8 +257,8 @@ export const speciesMapping: SpeciesMap = {
|
|||
'terrier', 'bull[ -]?terrier', 'australian[ -]?shepherd', 'australian[ -]?shep[h]?ard', 'german[ -]?shep[h]?([ea]rd)?',
|
||||
'malinois', 'woof', 'labrador', 'collie', 'canis', 'lupus', 'canid', 'chihuahua', 'poodle', 'chinchilla',
|
||||
'chow[ -]?chow', 'corgi', 'anubis', 'beagle', '.*wolf', 'direwolf', 'pointer', 'dhole', 'worg(en)?',
|
||||
'anubian', 'dalmatian', 'dalmation', 'inumimi', 'lupine', 'malamute', 'mastiff', 'mutt', 'rottweill?er', 'shih[ -]?tzu',
|
||||
'vallhund', 'puppy', 'oo?kami', 'great[ -]?dane', 'golden[ -]?(retriever|lab|labrador)', 'cocker[ -]?spaniel', 'samoyed', 'awoo',
|
||||
'anubian', 'dalmatian', 'dalmation', 'inumimi', 'lupine', 'malamute', 'mastiff', 'mutt', 'rott?w[ea]ill?er', 'shih[ -]?tzu',
|
||||
'vallhund', 'puppy', 'oo?kami', 'great[ -]?dane', 'golden[ -]?(retriever|lab|labrador)', 'cocker[ -]?spaniel', 'samm?oyed', 'awoo+',
|
||||
'borzoi', 'spaniel', 'ookamimimi', 'jakkarumimi', 'chinchiramimi', 'woffo', 'wuff', 'wolfdog', 'setter', 'papillon',
|
||||
'🐶', '🐺', '🐕', '🐩', 'aussie[ -]?doodle', 'shiba', 'inu', 'veil[ -]?hound', 'timber[ -]?wolf', 'hell[ -]?hound', 'hound',
|
||||
'kangal', 'behemoth', 'mongrel', 'fenrir', 'v[aá]na[r]?gand[r]?', 'crux', 'st.?[ -]?bernard',
|
||||
|
@ -313,7 +313,7 @@ export const speciesMapping: SpeciesMap = {
|
|||
'vaporeon', 'reshiram', 'quilava', 'decidueye', 'marshadow', 'weavile', 'zubat', 'buizel', 'latias', 'nidorina',
|
||||
'chandelur(e|ia)', 'sneasel', 'rockruff', 'lugia', 'komala', 'meowstic', 'leafeon', 'purrloin', 'pokemorph',
|
||||
'houndour', 'zoroark', 'mightyena', 'mew', 'nidoqueen', 'zangoose', 'goodra', 'flygon', 'dialga', 'pansear',
|
||||
'bibarel', 'charmeleon',
|
||||
'bibarel', 'charmeleon', 'lapras',
|
||||
|
||||
// digimon
|
||||
'gatomon', 'impmon', 'guilmon'
|
||||
|
@ -439,7 +439,7 @@ export const speciesMapping: SpeciesMap = {
|
|||
'hutt', 'klyntar', 'twi\'?lek', 'sangheili', 'salarian', 't[\']?vaoan', 'yautja', 'zabrak'],
|
||||
|
||||
[Species.Robot]: ['android', 'cyborg', 'gynoid', 'automaton', 'robot', 'transformer', 'cybertronian', 'reploid', 'synth', 'ai',
|
||||
'realian', 'replicant'],
|
||||
'realian', 'replicant', 'synthetic'],
|
||||
|
||||
[Species.Hub]: ['hub', 'varies', 'various', 'variable', 'many', 'flexible', 'any', 'partner preference']
|
||||
};
|
||||
|
|
183
learn/matcher.ts
183
learn/matcher.ts
|
@ -31,6 +31,7 @@ export interface MatchReport {
|
|||
themMultiSpecies: boolean;
|
||||
merged: MatchResultScores;
|
||||
score: Scoring | null;
|
||||
details: MatchScoreDetails;
|
||||
}
|
||||
|
||||
export interface MatchResultCharacterInfo {
|
||||
|
@ -48,6 +49,11 @@ export interface MatchResultScores {
|
|||
[TagId.Species]: Score;
|
||||
}
|
||||
|
||||
export interface MatchScoreDetails {
|
||||
totalScoreDimensions: number;
|
||||
dimensionsAtScoreLevel: number;
|
||||
}
|
||||
|
||||
export interface MatchResult {
|
||||
you: Character,
|
||||
them: Character,
|
||||
|
@ -171,6 +177,8 @@ export class Matcher {
|
|||
readonly yourAnalysis: CharacterAnalysis;
|
||||
readonly theirAnalysis: CharacterAnalysis;
|
||||
|
||||
static readonly UNICORN_LEVEL = 5.5;
|
||||
|
||||
|
||||
constructor(you: Character, them: Character, yourAnalysis?: CharacterAnalysis, theirAnalysis?: CharacterAnalysis) {
|
||||
this.you = you;
|
||||
|
@ -196,11 +204,18 @@ export class Matcher {
|
|||
youMultiSpecies: false,
|
||||
themMultiSpecies: false,
|
||||
merged: Matcher.mergeResults(youThemMatch, themYouMatch),
|
||||
score: null
|
||||
score: null,
|
||||
details: {
|
||||
totalScoreDimensions: 0,
|
||||
dimensionsAtScoreLevel: 0
|
||||
}
|
||||
};
|
||||
|
||||
report.score = Matcher.calculateReportScore(report);
|
||||
|
||||
report.details.totalScoreDimensions = Matcher.countScoresTotal(report);
|
||||
report.details.dimensionsAtScoreLevel = Matcher.countScoresAtLevel(report, report.score) || 0;
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
|
@ -228,13 +243,20 @@ export class Matcher {
|
|||
youMultiSpecies: (yourCharacterAnalyses.length > 1),
|
||||
themMultiSpecies: (theirCharacterAnalyses.length > 1),
|
||||
merged: Matcher.mergeResults(youThemMatch, themYouMatch),
|
||||
score: null
|
||||
score: null,
|
||||
details: {
|
||||
totalScoreDimensions: 0,
|
||||
dimensionsAtScoreLevel: 0
|
||||
}
|
||||
};
|
||||
|
||||
report.score = Matcher.calculateReportScore(report);
|
||||
|
||||
const scoreLevelCount = Matcher.countScoresAtLevel(report, report.score);
|
||||
|
||||
report.details.totalScoreDimensions = Matcher.countScoresTotal(report);
|
||||
report.details.dimensionsAtScoreLevel = scoreLevelCount || 0;
|
||||
|
||||
if (
|
||||
(bestScore === null)
|
||||
|| (
|
||||
|
@ -251,7 +273,10 @@ export class Matcher {
|
|||
}
|
||||
}
|
||||
|
||||
log.debug('report.identify.best', {buildTime: Date.now() - reportStartTime});
|
||||
log.debug(
|
||||
'report.identify.best',
|
||||
{buildTime: Date.now() - reportStartTime, variations: yourCharacterAnalyses.length * theirCharacterAnalyses.length}
|
||||
);
|
||||
|
||||
return bestReport!;
|
||||
}
|
||||
|
@ -301,21 +326,6 @@ export class Matcher {
|
|||
);
|
||||
}
|
||||
|
||||
static countScoresAtLevel(m: MatchReport, scoreLevel: Scoring | null): number | null {
|
||||
if (scoreLevel === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const yourScores = _.values(m.you.scores);
|
||||
const theirScores = _.values(m.them.scores);
|
||||
|
||||
return _.reduce(
|
||||
_.concat(yourScores, theirScores),
|
||||
(accum: number, score: Score) => accum + (score.score === scoreLevel ? 1 : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static calculateReportScore(m: MatchReport): Scoring | null {
|
||||
const yourScores = _.values(m.you.scores);
|
||||
const theirScores = _.values(m.them.scores);
|
||||
|
@ -647,7 +657,23 @@ export class Matcher {
|
|||
if ((!yourSubDomRole) || (!theirSubDomRole))
|
||||
return new Score(Scoring.NEUTRAL);
|
||||
|
||||
if ((yourSubDomRole === SubDomRole.AlwaysDominant) || (yourSubDomRole === SubDomRole.UsuallyDominant)) {
|
||||
if (yourSubDomRole === SubDomRole.UsuallyDominant) {
|
||||
if (theirSubDomRole === SubDomRole.Switch)
|
||||
return new Score(Scoring.MATCH, `Loves <span>switches</span>`);
|
||||
|
||||
if ((theirSubDomRole === SubDomRole.AlwaysSubmissive) || (theirSubDomRole === SubDomRole.UsuallySubmissive))
|
||||
return new Score(Scoring.MATCH, `Loves <span>submissives</span>`);
|
||||
|
||||
if (yourRoleReversalPreference === KinkPreference.Favorite)
|
||||
return new Score(Scoring.MATCH, `Loves <span>role reversal</span>`);
|
||||
|
||||
if (yourRoleReversalPreference === KinkPreference.Yes)
|
||||
return new Score(Scoring.MATCH, `Likes <span>role reversal</span>`);
|
||||
|
||||
return new Score(Scoring.WEAK_MISMATCH, 'Hesitant about <span>dominants</span>');
|
||||
}
|
||||
|
||||
if (yourSubDomRole === SubDomRole.AlwaysDominant) {
|
||||
if (theirSubDomRole === SubDomRole.Switch)
|
||||
return new Score(Scoring.WEAK_MATCH, `Likes <span>switches</span>`);
|
||||
|
||||
|
@ -666,7 +692,23 @@ export class Matcher {
|
|||
return new Score(Scoring.WEAK_MISMATCH, 'Hesitant about <span>dominants</span>');
|
||||
}
|
||||
|
||||
if ((yourSubDomRole === SubDomRole.AlwaysSubmissive) || (yourSubDomRole === SubDomRole.UsuallySubmissive)) {
|
||||
if (yourSubDomRole === SubDomRole.UsuallySubmissive) {
|
||||
if (theirSubDomRole === SubDomRole.Switch)
|
||||
return new Score(Scoring.MATCH, `Loves <span>switches</span>`);
|
||||
|
||||
if ((theirSubDomRole === SubDomRole.AlwaysDominant) || (theirSubDomRole === SubDomRole.UsuallyDominant))
|
||||
return new Score(Scoring.MATCH, `Loves <span>dominants</span>`);
|
||||
|
||||
if (yourRoleReversalPreference === KinkPreference.Favorite)
|
||||
return new Score(Scoring.MATCH, `Loves <span>role reversal</span>`);
|
||||
|
||||
if (yourRoleReversalPreference === KinkPreference.Yes)
|
||||
return new Score(Scoring.MATCH, `Likes <span>role reversal</span>`);
|
||||
|
||||
return new Score(Scoring.WEAK_MISMATCH, 'Hesitant about <span>submissives</span>');
|
||||
}
|
||||
|
||||
if (yourSubDomRole === SubDomRole.AlwaysSubmissive) {
|
||||
if (theirSubDomRole === SubDomRole.Switch)
|
||||
return new Score(Scoring.WEAK_MATCH, `Likes <span>switches</span>`);
|
||||
|
||||
|
@ -902,4 +944,105 @@ export class Matcher {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static countScoresAtLevel(
|
||||
m: MatchReport, scoreLevel: Scoring | null,
|
||||
skipYours: boolean = false,
|
||||
skipTheirs: boolean = false
|
||||
): number | null {
|
||||
if (scoreLevel === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const yourScores = skipYours ? [] : _.values(m.you.scores);
|
||||
const theirScores = skipTheirs ? [] : _.values(m.them.scores);
|
||||
|
||||
return _.reduce(
|
||||
_.concat(yourScores, theirScores),
|
||||
(accum: number, score: Score) => accum + (score.score === scoreLevel ? 1 : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static countScoresAboveLevel(
|
||||
m: MatchReport, scoreLevel: Scoring | null,
|
||||
skipYours: boolean = false,
|
||||
skipTheirs: boolean = false
|
||||
): number {
|
||||
if (scoreLevel === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const yourScores = skipYours ? [] : _.values(m.you.scores);
|
||||
const theirScores = skipTheirs ? [] : _.values(m.them.scores);
|
||||
|
||||
return _.reduce(
|
||||
_.concat(yourScores, theirScores),
|
||||
(accum: number, score: Score) => accum + ((score.score > scoreLevel) && (score.score !== Scoring.NEUTRAL) ? 1 : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static countScoresTotal(m: MatchReport): number {
|
||||
return _.values(m.you.scores).length
|
||||
+ _.values(m.them.scores).length;
|
||||
}
|
||||
|
||||
static calculateSearchScoreForMatch(
|
||||
score: Scoring,
|
||||
match: MatchReport
|
||||
): number {
|
||||
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;
|
||||
|
||||
let atLevelScore = 0;
|
||||
let aboveLevelScore = 0;
|
||||
|
||||
let theirAtLevelDimensions = 0;
|
||||
let atLevelMul = 0;
|
||||
let theirAboveLevelDimensions = 0;
|
||||
let aboveLevelMul = 0;
|
||||
|
||||
if ((dimensionsAtScoreLevel > 0) && (totalScoreDimensions > 0)) {
|
||||
const matchRatio = dimensionsAtScoreLevel / totalScoreDimensions;
|
||||
theirAtLevelDimensions = Matcher.countScoresAtLevel(match, score, true, false) || 0;
|
||||
|
||||
// 1.0 == bad balance; 0.0 == ideal balance
|
||||
atLevelMul = Math.abs((theirAtLevelDimensions / (dimensionsAtScoreLevel)) - 0.5) * 2;
|
||||
|
||||
atLevelScore = (1 - (atLevelMul * 0.5)) * Math.pow(dimensionsAtScoreLevel, matchRatio);
|
||||
}
|
||||
|
||||
if ((dimensionsAboveScoreLevel > 0) && (totalScoreDimensions > 0)) {
|
||||
const matchRatio = dimensionsAboveScoreLevel / totalScoreDimensions;
|
||||
|
||||
theirAboveLevelDimensions = Matcher.countScoresAboveLevel(match, score, true, false) || 0;
|
||||
|
||||
// 1.0 == bad balance; 0.0 == ideal balance
|
||||
aboveLevelMul = Math.abs((theirAboveLevelDimensions / (dimensionsAboveScoreLevel)) - 0.5) * 2;
|
||||
|
||||
aboveLevelScore = (1 - (aboveLevelMul * 0.5)) * Math.pow(dimensionsAboveScoreLevel, matchRatio);
|
||||
}
|
||||
|
||||
log.debug(
|
||||
'report.score.search',
|
||||
{
|
||||
you: match.you.you.name,
|
||||
them: match.them.you.name,
|
||||
searchScore: (atLevelScore + aboveLevelScore),
|
||||
atLevelScore,
|
||||
aboveLevelScore,
|
||||
atLevelMul,
|
||||
aboveLevelMul,
|
||||
dimensionsAboveScoreLevel,
|
||||
dimensionsAtScoreLevel,
|
||||
theirAtLevelDimensions,
|
||||
theirAboveLevelDimensions
|
||||
}
|
||||
);
|
||||
|
||||
return (atLevelScore + aboveLevelScore);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as _ from 'lodash';
|
|||
import core from '../chat/core';
|
||||
import {Character as ComplexCharacter, CharacterGroup, Guestbook} from '../site/character_page/interfaces';
|
||||
import { AsyncCache } from './async-cache';
|
||||
import { Matcher, Scoring } from './matcher';
|
||||
import { Matcher, MatchReport, Scoring } from './matcher';
|
||||
import { PermanentIndexedStore } from './store/sql-store';
|
||||
import { CharacterImage, SimpleCharacter } from '../interfaces';
|
||||
|
||||
|
@ -16,7 +16,6 @@ export interface MetaRecord {
|
|||
lastMetaFetched: Date | null;
|
||||
}
|
||||
|
||||
|
||||
export interface CountRecord {
|
||||
groupCount: number | null;
|
||||
friendCount: number | null;
|
||||
|
@ -24,12 +23,20 @@ export interface CountRecord {
|
|||
lastCounted: number | null;
|
||||
}
|
||||
|
||||
export interface CharacterMatchSummary {
|
||||
matchScore: number;
|
||||
// dimensionsAtScoreLevel: number;
|
||||
// dimensionsAboveScoreLevel: number;
|
||||
// totalScoreDimensions: number;
|
||||
searchScore: number;
|
||||
}
|
||||
|
||||
export interface CharacterCacheRecord {
|
||||
character: ComplexCharacter;
|
||||
lastFetched: Date;
|
||||
added: Date;
|
||||
matchScore: number;
|
||||
// counts?: CountRecord;
|
||||
match: CharacterMatchSummary;
|
||||
meta?: MetaRecord;
|
||||
}
|
||||
|
||||
|
@ -135,12 +142,19 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
|
||||
async register(c: ComplexCharacter, skipStore: boolean = false): Promise<CharacterCacheRecord> {
|
||||
const k = AsyncCache.nameKey(c.character.name);
|
||||
const score = ProfileCache.score(c);
|
||||
const match = ProfileCache.match(c);
|
||||
const score = (!match || match.score === null) ? Scoring.NEUTRAL : match.score;
|
||||
|
||||
if (score === 0) {
|
||||
console.log(`Storing score 0 for character ${c.character.name}`);
|
||||
}
|
||||
|
||||
// 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;
|
||||
const searchScore = match ? Matcher.calculateSearchScoreForMatch(score, match) : 0;
|
||||
const matchDetails = { matchScore: score, searchScore };
|
||||
|
||||
if ((this.store) && (!skipStore)) {
|
||||
await this.store.storeProfile(c);
|
||||
}
|
||||
|
@ -150,7 +164,7 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
|
||||
rExisting.character = c;
|
||||
rExisting.lastFetched = new Date();
|
||||
rExisting.matchScore = score;
|
||||
rExisting.match = matchDetails;
|
||||
|
||||
return rExisting;
|
||||
}
|
||||
|
@ -159,7 +173,7 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
character: c,
|
||||
lastFetched: new Date(),
|
||||
added: new Date(),
|
||||
matchScore: score
|
||||
match: matchDetails
|
||||
};
|
||||
|
||||
this.cache[k] = rNew;
|
||||
|
@ -168,15 +182,13 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
}
|
||||
|
||||
|
||||
static score(c: ComplexCharacter): number {
|
||||
static match(c: ComplexCharacter): MatchReport | null {
|
||||
const you = core.characters.ownProfile;
|
||||
|
||||
if (!you) {
|
||||
return 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
const m = Matcher.identifyBestMatchReport(you.character, c.character);
|
||||
|
||||
return m.score === null ? Scoring.NEUTRAL : m.score;
|
||||
return Matcher.identifyBestMatchReport(you.character, c.character);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue