Smart filters
This commit is contained in:
parent
fdc7bec43d
commit
65ab5ffa32
|
@ -1,5 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
## Canary
|
||||
* Added a way to hide/filter out characters, messages, and ads (Settings > Identity Politics)
|
||||
|
||||
## 1.16.2
|
||||
* Fixed broken auto-ads
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@
|
|||
core.connection.onMessage('FKS', async (data) => {
|
||||
const results = data.characters.map((x) => ({ character: core.characters.get(x), profile: null }))
|
||||
.filter((x) => core.state.hiddenUsers.indexOf(x.character.name) === -1 && !x.character.isIgnored)
|
||||
.filter((x) => this.isSpeciesMatch(x) && this.isBodyTypeMatch(x))
|
||||
.filter((x) => this.isSpeciesMatch(x) && this.isBodyTypeMatch(x) && this.isSmartFilterMatch(x))
|
||||
.sort(sort);
|
||||
|
||||
// pre-warm cache
|
||||
|
@ -399,6 +399,14 @@
|
|||
return this.data.bodytypes.indexOf(bodytype!.value) > -1
|
||||
}
|
||||
|
||||
isSmartFilterMatch(result: SearchResult) {
|
||||
if (!core.state.settings.risingFilter.hideSearchResults) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return result.profile ? !result.profile?.match.isFiltered : true;
|
||||
}
|
||||
|
||||
getSpeciesOptions(): SearchSpecies[] {
|
||||
const species = _.map(
|
||||
speciesMapping,
|
||||
|
|
|
@ -449,16 +449,26 @@
|
|||
|
||||
/* tslint:disable */
|
||||
getMessageWrapperClasses(): any {
|
||||
const filter = core.state.settings.risingFilter;
|
||||
const classes:any = {};
|
||||
|
||||
if (this.isPrivate(this.conversation)) {
|
||||
classes['filter-channel-messages'] = filter.hidePrivateMessages;
|
||||
return classes;
|
||||
}
|
||||
|
||||
if (!this.isChannel(this.conversation)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const conv = <Conversation.ChannelConversation>this.conversation;
|
||||
const classes:any = {};
|
||||
|
||||
classes['messages-' + conv.mode] = true;
|
||||
classes['hide-non-matching'] = !this.showNonMatchingAds;
|
||||
|
||||
classes['filter-ads'] = filter.hideAds;
|
||||
classes['filter-channel-messages'] = conv.channel.owner !== '' ? filter.hidePrivateChannelMessages : filter.hidePublicChannelMessages;
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
|
@ -839,12 +849,26 @@
|
|||
}
|
||||
|
||||
|
||||
.messages.hide-non-matching .message.message-score {
|
||||
.messages.hide-non-matching .message.message-score,
|
||||
{
|
||||
&.mismatch {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.messages.filter-ads {
|
||||
.message.filter-match.message-ad {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.messages.filter-channel-messages {
|
||||
.message.filter-match.message-message,
|
||||
.message.filter-match.message-action {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
.message-pre {
|
||||
font-size: 75%;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<modal :action="l('settings.action')" @submit="submit" @open="load()" id="settings" dialogClass="w-100">
|
||||
<tabs style="flex-shrink:0;margin-bottom:10px" v-model="selectedTab"
|
||||
:tabs="[l('settings.tabs.general'), l('settings.tabs.notifications'), 'F-Chat Rising 🦄', l('settings.tabs.hideAds'), l('settings.tabs.import')]"></tabs>
|
||||
:tabs="[l('settings.tabs.general'), l('settings.tabs.notifications'), 'F-Chat Rising 🦄', 'Identity Politics 🦄', l('settings.tabs.hideAds'), l('settings.tabs.import')]"></tabs>
|
||||
<div v-show="selectedTab === '0'">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="disallowedTags">{{l('settings.disallowedTags')}}</label>
|
||||
|
@ -200,7 +200,83 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-show="selectedTab === '3'">
|
||||
<h5>Visibility</h5>
|
||||
|
||||
<div class="form-group filters">
|
||||
<label class="control-label" for="risingFilter.hideAds">
|
||||
<input type="checkbox" id="risingFilter.hideAds" v-model="risingFilter.hideAds"/>
|
||||
Hide <b>ads</b> from matching characters
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.hideSearchResults">
|
||||
<input type="checkbox" id="risingFilter.hideSearchResults" v-model="risingFilter.hideSearchResults"/>
|
||||
Hide matching characters from <b>search results</b>
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.hideChannelMembers">
|
||||
<input type="checkbox" id="risingFilter.hideChannelMembers" v-model="risingFilter.hideChannelMembers"/>
|
||||
Hide matching characters from <b>channel members lists</b>
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.hidePublicChannelMessages">
|
||||
<input type="checkbox" id="risingFilter.hidePublicChannelMessages" v-model="risingFilter.hidePublicChannelMessages"/>
|
||||
Hide <b>public channel messages</b> from matching characters
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.hidePrivateChannelMessages">
|
||||
<input type="checkbox" id="risingFilter.hidePrivateChannelMessages" v-model="risingFilter.hidePrivateChannelMessages"/>
|
||||
Hide <b>private channel messages</b> from matching characters
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.hidePrivateMessages">
|
||||
<input type="checkbox" id="risingFilter.hidePrivateMessages" v-model="risingFilter.hidePrivateMessages"/>
|
||||
Hide <b>private messages</b> (PMs) from matching characters
|
||||
</label>
|
||||
|
||||
<label class="control-label" for="risingFilter.penalizeMatches">
|
||||
<input type="checkbox" id="risingFilter.penalizeMatches" v-model="risingFilter.penalizeMatches"/>
|
||||
Penalize <b>match scores</b> for matching characters
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group filters">
|
||||
<label class="control-label" for="risingFilter.autoReply">
|
||||
<input type="checkbox" id="risingFilter.autoReply" v-model="risingFilter.autoReply"/>
|
||||
Send an automatic 'no thank you' response to matching characters if they message you
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h5>Character Age Match</h5>
|
||||
<div class="form-group">Leave empty for no limit.</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="risingFilter.minAge">Characters younger than</label>
|
||||
<input id="risingFilter.minAge" type="number" class="form-control" v-model="risingFilter.minAge"/>
|
||||
|
||||
<label class="control-label" for="risingFilter.maxAge">Characters older than</label>
|
||||
<input id="risingFilter.maxAge" type="number" class="form-control" v-model="risingFilter.maxAge"/>
|
||||
</div>
|
||||
|
||||
<h5>Type Match</h5>
|
||||
<div class="form-group filters" >
|
||||
<label class="control-label" :for="'risingFilter.smartFilters.' + key" v-for="(value, key) in smartFilterTypes">
|
||||
<input type="checkbox" :id="'risingFilter.smartFilters.' + key" v-bind:checked="getSmartFilter(key)" @change="(v) => setSmartFilter(key, v)"/>
|
||||
{{value.name}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h5>Exception List</h5>
|
||||
<div class="form-group">Filters are not applied to these character names. Separate names with a linefeed.</div>
|
||||
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" :value="getExceptionList()" @change="(v) => setExceptionList(v)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-show="selectedTab === '4'">
|
||||
<template v-if="hidden.length">
|
||||
<div v-for="(user, i) in hidden">
|
||||
<span class="fa fa-times" style="cursor:pointer" @click.stop="hidden.splice(i, 1)"></span>
|
||||
|
@ -209,7 +285,7 @@
|
|||
</template>
|
||||
<template v-else>{{l('settings.hideAds.empty')}}</template>
|
||||
</div>
|
||||
<div v-show="selectedTab === '4'" style="display:flex;padding-top:10px">
|
||||
<div v-show="selectedTab === '5'" style="display:flex;padding-top:10px">
|
||||
<select id="import" class="form-control" v-model="importCharacter" style="flex:1;margin-right:10px">
|
||||
<option value="">{{l('settings.import.selectCharacter')}}</option>
|
||||
<option v-for="character in availableImports" :value="character">{{character}}</option>
|
||||
|
@ -227,6 +303,10 @@
|
|||
import core from './core';
|
||||
import {Settings as SettingsInterface} from './interfaces';
|
||||
import l from './localize';
|
||||
import { SmartFilterSettings, SmartFilterSelection } from '../learn/filter/types';
|
||||
import { smartFilterTypes as smartFilterTypesOrigin } from '../learn/filter/types';
|
||||
import _ from 'lodash';
|
||||
import { matchesSmartFilters } from '../learn/filter/smart-filter';
|
||||
|
||||
@Component({
|
||||
components: {modal: Modal, tabs: Tabs}
|
||||
|
@ -269,6 +349,9 @@
|
|||
risingShowUnreadOfflineCount!: boolean;
|
||||
risingColorblindMode!: boolean;
|
||||
|
||||
risingFilter!: SmartFilterSettings = {} as any;
|
||||
|
||||
smartFilterTypes = smartFilterTypesOrigin;
|
||||
|
||||
async load(): Promise<void> {
|
||||
const settings = core.state.settings;
|
||||
|
@ -305,6 +388,7 @@
|
|||
this.risingShowUnreadOfflineCount = settings.risingShowUnreadOfflineCount;
|
||||
|
||||
this.risingColorblindMode = settings.risingColorblindMode;
|
||||
this.risingFilter = settings.risingFilter;
|
||||
}
|
||||
|
||||
async doImport(): Promise<void> {
|
||||
|
@ -325,8 +409,14 @@
|
|||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
const oldRisingFilter = JSON.parse(JSON.stringify(core.state.settings.risingFilter));
|
||||
|
||||
const idleTimer = parseInt(this.idleTimer, 10);
|
||||
const fontSize = parseFloat(this.fontSize);
|
||||
|
||||
const minAge = this.getAsNumber(this.risingFilter.minAge);
|
||||
const maxAge = this.getAsNumber(this.risingFilter.maxAge);
|
||||
|
||||
core.state.settings = {
|
||||
playSound: this.playSound,
|
||||
clickOpensMessage: this.clickOpensMessage,
|
||||
|
@ -360,9 +450,63 @@
|
|||
risingShowUnreadOfflineCount: this.risingShowUnreadOfflineCount,
|
||||
|
||||
risingColorblindMode: this.risingColorblindMode,
|
||||
risingFilter: {
|
||||
...this.risingFilter,
|
||||
minAge: (minAge !== null && maxAge !== null) ? Math.min(minAge, maxAge) : minAge,
|
||||
maxAge: (minAge !== null && maxAge !== null) ? Math.max(minAge, maxAge) : maxAge
|
||||
}
|
||||
};
|
||||
|
||||
console.log('SETTINGS', minAge, maxAge, core.state.settings);
|
||||
|
||||
const newRisingFilter = JSON.parse(JSON.stringify(core.state.settings.risingFilter));
|
||||
|
||||
if (!_.isEqual(oldRisingFilter, newRisingFilter)) {
|
||||
this.rebuildFilters();
|
||||
}
|
||||
|
||||
if(this.notifications) await core.notifications.requestPermission();
|
||||
}
|
||||
|
||||
rebuildFilters() {
|
||||
core.cache.profileCache.onEachInMemory(
|
||||
(c) => {
|
||||
const oldFiltered = c.match.isFiltered;
|
||||
|
||||
c.match.isFiltered = matchesSmartFilters(c.character.character, core.state.settings.risingFilter);
|
||||
|
||||
if (oldFiltered !== c.match.isFiltered) {
|
||||
core.cache.populateAllConversationsWithScore(c.character.character.name, c.match.matchScore, c.match.isFiltered);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getAsNumber(input: any): number | null {
|
||||
if (_.isNil(input) || input === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const n = parseInt(input, 10);
|
||||
|
||||
return !Number.isNaN(n) && Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
getExceptionList(): string {
|
||||
return _.join(this.risingFilter.exceptionNames, '\n');
|
||||
}
|
||||
|
||||
setExceptionList(v: any): void {
|
||||
this.risingFilter.exceptionNames = _.map(_.split(v.target.value), (v) => _.trim(v));
|
||||
}
|
||||
|
||||
getSmartFilter(key: keyof SmartFilterSelection): boolean {
|
||||
return !!this.risingFilter.smartFilters?.[key];
|
||||
}
|
||||
|
||||
setSmartFilter(key: keyof SmartFilterSelection , value: any): void {
|
||||
this.risingFilter.smartFilters[key] = value.target.checked;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -371,4 +515,11 @@
|
|||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#settings .form-group.filters label {
|
||||
display: list-item;
|
||||
margin: 0;
|
||||
margin-left: 5px;
|
||||
list-style: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -61,9 +61,26 @@
|
|||
}
|
||||
|
||||
get filteredMembers(): ReadonlyArray<Channel.Member> {
|
||||
if(this.filter.length === 0) return this.channel.sortedMembers;
|
||||
const filter = new RegExp(this.filter.replace(/[^\w]/gi, '\\$&'), 'i');
|
||||
return this.channel.sortedMembers.filter((member) => filter.test(member.character.name));
|
||||
const members = this.prefilterMembers();
|
||||
|
||||
if (!core.state.settings.risingFilter.hideChannelMembers) {
|
||||
return members;
|
||||
}
|
||||
|
||||
return members.filter((m) => {
|
||||
const p = core.cache.profileCache.getSync(m.character.name);
|
||||
|
||||
return !p || !p.match.isFiltered;
|
||||
});
|
||||
}
|
||||
|
||||
prefilterMembers(): ReadonlyArray<Channel.Member> {
|
||||
if(this.filter.length === 0)
|
||||
return this.channel.sortedMembers;
|
||||
|
||||
const filter = new RegExp(this.filter.replace(/[^\w]/gi, '\\$&'), 'i');
|
||||
|
||||
return this.channel.sortedMembers.filter((member) => filter.test(member.character.name));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -106,4 +123,4 @@
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -54,6 +54,41 @@ export class Settings implements ISettings {
|
|||
|
||||
risingShowUnreadOfflineCount = true;
|
||||
risingColorblindMode = false;
|
||||
|
||||
risingFilter = {
|
||||
hideAds: false,
|
||||
hideSearchResults: false,
|
||||
hideChannelMembers: false,
|
||||
hidePublicChannelMessages: false,
|
||||
hidePrivateChannelMessages: false,
|
||||
hidePrivateMessages: false,
|
||||
penalizeMatches: false,
|
||||
autoReply: true,
|
||||
minAge: null,
|
||||
maxAge: null,
|
||||
smartFilters: {
|
||||
ageplay: false,
|
||||
anthro: false,
|
||||
feral: false,
|
||||
human: false,
|
||||
hyper: false,
|
||||
incest: false,
|
||||
microMacro: false,
|
||||
obesity: false,
|
||||
pokemon: false,
|
||||
pregnancy: false,
|
||||
rape: false,
|
||||
scat: false,
|
||||
std: false,
|
||||
taur: false,
|
||||
gore: false,
|
||||
vore: false,
|
||||
unclean: false,
|
||||
watersports: false,
|
||||
zoophilia: false
|
||||
},
|
||||
exceptionNames: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,6 +149,7 @@ export class Message implements Conversation.ChatMessage {
|
|||
isHighlight = false;
|
||||
|
||||
score = 0;
|
||||
filterMatch = false;
|
||||
|
||||
constructor(readonly type: Conversation.Message.Type, readonly sender: Character, readonly text: string,
|
||||
readonly time: Date = new Date()) {
|
||||
|
@ -126,6 +162,7 @@ export class EventMessage implements Conversation.EventMessage {
|
|||
readonly type = Conversation.Message.Type.Event;
|
||||
|
||||
readonly score = 0;
|
||||
filterMatch = false;
|
||||
|
||||
constructor(readonly text: string, readonly time: Date = new Date()) {
|
||||
}
|
||||
|
|
|
@ -4,14 +4,16 @@ import {decodeHTML} from '../fchat/common';
|
|||
import { AdManager } from './ads/ad-manager';
|
||||
import { characterImage, ConversationSettings, EventMessage, Message, messageToString } from './common';
|
||||
import core from './core';
|
||||
import {Channel, Character, Conversation as Interfaces} from './interfaces';
|
||||
import { Channel, Character, Conversation as Interfaces } from './interfaces';
|
||||
import l from './localize';
|
||||
import {CommandContext, isAction, isCommand, isWarn, parse as parseCommand} from './slash_commands';
|
||||
import MessageType = Interfaces.Message.Type;
|
||||
import {EventBus} from './preview/event-bus';
|
||||
import throat from 'throat';
|
||||
import Bluebird from 'bluebird';
|
||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
|
||||
import log from 'electron-log';
|
||||
import isChannel = Interfaces.isChannel;
|
||||
import isPrivate = Interfaces.isPrivate; //tslint:disable-line:match-default-export-name
|
||||
|
||||
function createMessage(this: any, type: MessageType, sender: Character, text: string, time?: Date): Message {
|
||||
if(type === MessageType.Message && isAction(text)) {
|
||||
|
@ -45,7 +47,7 @@ abstract class Conversation implements Interfaces.Conversation {
|
|||
// private loadedMore = false;
|
||||
adManager: AdManager;
|
||||
|
||||
protected static readonly conversationThroat = throat(1); // make sure user posting and ad posting won't get in each others' way
|
||||
public static readonly conversationThroat = throat(1); // make sure user posting and ad posting won't get in each others' way
|
||||
|
||||
constructor(readonly key: string, public _isPinned: boolean) {
|
||||
this.adManager = new AdManager(this);
|
||||
|
@ -161,7 +163,7 @@ abstract class Conversation implements Interfaces.Conversation {
|
|||
|
||||
protected static readonly POST_DELAY = 1250;
|
||||
|
||||
protected static async testPostDelay(): Promise<void> {
|
||||
public static async testPostDelay(): Promise<void> {
|
||||
const lastPostDelta = Date.now() - core.cache.getLastPost().getTime();
|
||||
|
||||
// console.log('Last Post Delta', lastPostDelta, ((lastPostDelta < Conversation.POST_DELAY) && (lastPostDelta > 0)));
|
||||
|
@ -691,9 +693,41 @@ export default function(this: any): Interfaces.State {
|
|||
const conversation = state.channelMap[data.channel.toLowerCase()];
|
||||
if(conversation === undefined) return core.channels.leave(data.channel);
|
||||
if(char.isIgnored) return;
|
||||
|
||||
const message = createMessage(MessageType.Message, char, decodeHTML(data.message), time);
|
||||
EventBus.$emit('channel-message', { message, channel: conversation });
|
||||
await conversation.addMessage(message);
|
||||
// message.type === MessageType.Message
|
||||
if (
|
||||
(isPrivate(conversation) && core.state.settings.risingFilter.hidePrivateMessages) ||
|
||||
(isChannel(conversation) && conversation.channel.owner === '' && core.state.settings.risingFilter.hidePublicChannelMessages) ||
|
||||
(isChannel(conversation) && conversation.channel.owner !== '' && core.state.settings.risingFilter.hidePrivateChannelMessages)
|
||||
) {
|
||||
const cachedProfile = core.cache.profileCache.getSync(char.name) || await core.cache.profileCache.get(char.name);
|
||||
|
||||
if (cachedProfile && isPrivate(conversation) && core.state.settings.risingFilter.autoReply && !cachedProfile.match.autoResponded) {
|
||||
cachedProfile.match.autoResponded = true;
|
||||
|
||||
log.debug('filter.autoresponse', { name: char.name });
|
||||
|
||||
void Conversation.conversationThroat(
|
||||
async() => {
|
||||
await Conversation.testPostDelay();
|
||||
|
||||
// tslint:disable-next-line:prefer-template
|
||||
const m = '[Automated message] Sorry, the player of this character has indicated that they are not interested in characters matching your profile. They will not see your message.\n\n' +
|
||||
'Need a filter for yourself? Try out [url=https://mrstallion.github.io/fchat-rising/]F-Chat Rising[/url]';
|
||||
|
||||
core.connection.send('PRI', {recipient: char.name, message: m});
|
||||
core.cache.markLastPostTime();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedProfile && cachedProfile.match.isFiltered) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const words = conversation.settings.highlightWords.slice();
|
||||
if(conversation.settings.defaultHighlights) words.push(...core.state.settings.highlightWords);
|
||||
|
|
10
chat/core.ts
10
chat/core.ts
|
@ -8,6 +8,7 @@ import {Channel, Character, Connection, Conversation, Logs, Notifications, Setti
|
|||
import { AdCoordinatorGuest } from './ads/ad-coordinator-guest';
|
||||
import { GeneralSettings } from '../electron/common';
|
||||
import { SiteSession } from '../site/site-session';
|
||||
import _ from 'lodash';
|
||||
|
||||
function createBBCodeParser(): BBCodeParser {
|
||||
const parser = new BBCodeParser();
|
||||
|
@ -73,7 +74,14 @@ const data = {
|
|||
vue.$watch(getter, callback);
|
||||
},
|
||||
async reloadSettings(): Promise<void> {
|
||||
state._settings = Object.assign(new SettingsImpl(), await core.settingsStore.get('settings'));
|
||||
const s = await core.settingsStore.get('settings');
|
||||
|
||||
state._settings = _.mergeWith(new SettingsImpl(), s, (oVal, sVal) => {
|
||||
if (_.isArray(oVal) && _.isArray(sVal)) {
|
||||
return sVal;
|
||||
}
|
||||
});
|
||||
|
||||
const hiddenUsers = await core.settingsStore.get('hiddenUsers');
|
||||
state.hiddenUsers = hiddenUsers !== undefined ? hiddenUsers : [];
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {Connection} from '../fchat';
|
|||
|
||||
import {Channel, Character} from '../fchat/interfaces';
|
||||
import { AdManager } from './ads/ad-manager';
|
||||
import { SmartFilterSettings } from '../learn/filter/types';
|
||||
export {Connection, Channel, Character} from '../fchat/interfaces';
|
||||
export const userStatuses: ReadonlyArray<Character.Status> = ['online', 'looking', 'away', 'busy', 'dnd'];
|
||||
export const channelModes: ReadonlyArray<Channel.Mode> = ['chat', 'ads', 'both'];
|
||||
|
@ -15,6 +16,7 @@ export namespace Conversation {
|
|||
readonly time: Date
|
||||
|
||||
score: number;
|
||||
filterMatch: boolean;
|
||||
}
|
||||
|
||||
export interface EventMessage extends BaseMessage {
|
||||
|
@ -221,6 +223,8 @@ export namespace Settings {
|
|||
|
||||
readonly risingShowUnreadOfflineCount: boolean;
|
||||
readonly risingColorblindMode: boolean;
|
||||
|
||||
readonly risingFilter: SmartFilterSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ const userPostfix: {[key: number]: string | undefined} = {
|
|||
let classes = `message message-${Conversation.Message.Type[message.type].toLowerCase()}` + (separators ? ' message-block' : '') +
|
||||
(message.type !== Conversation.Message.Type.Event && message.sender.name === core.connection.character ? ' message-own' : '') +
|
||||
((this.classes !== undefined) ? ` ${this.classes}` : '') +
|
||||
` ${this.scoreClasses}`;
|
||||
` ${this.scoreClasses}` +
|
||||
` ${this.filterClasses}`;
|
||||
if(message.type !== Conversation.Message.Type.Event) {
|
||||
children.push(
|
||||
(message.type === Conversation.Message.Type.Action) ? createElement('i', { class: 'message-pre fas fa-star' }) : '',
|
||||
|
@ -71,6 +72,7 @@ export default class MessageView extends Vue {
|
|||
readonly logs?: true;
|
||||
|
||||
scoreClasses = this.getMessageScoreClasses(this.message);
|
||||
filterClasses = this.getMessageFilterClasses(this.message);
|
||||
|
||||
scoreWatcher: (() => void) | null = ((this.message.type === Conversation.Message.Type.Ad) && (this.message.score === 0))
|
||||
? this.$watch('message.score', () => this.scoreUpdate())
|
||||
|
@ -91,11 +93,13 @@ export default class MessageView extends Vue {
|
|||
|
||||
// @Watch('message.score')
|
||||
scoreUpdate(): void {
|
||||
const oldClasses = this.scoreClasses;
|
||||
const oldScoreClasses = this.scoreClasses;
|
||||
const oldFilterClasses = this.filterClasses;
|
||||
|
||||
this.scoreClasses = this.getMessageScoreClasses(this.message);
|
||||
this.filterClasses = this.getMessageFilterClasses(this.message);
|
||||
|
||||
if (this.scoreClasses !== oldClasses) {
|
||||
if (this.scoreClasses !== oldScoreClasses || this.filterClasses !== oldFilterClasses) {
|
||||
this.$forceUpdate();
|
||||
}
|
||||
|
||||
|
@ -115,4 +119,11 @@ export default class MessageView extends Vue {
|
|||
return `message-score ${Score.getClasses(message.score as Scoring)}`;
|
||||
}
|
||||
|
||||
getMessageFilterClasses(message: Conversation.Message): string {
|
||||
if (!message.filterMatch) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'filter-match';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export class CacheManager {
|
|||
const c = await this.profileCache.get(name);
|
||||
|
||||
if (c) {
|
||||
this.updateAdScoringForProfile(c.character, c.match.matchScore);
|
||||
this.updateAdScoringForProfile(c.character, c.match.matchScore, c.match.isFiltered);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ export class CacheManager {
|
|||
const c = await methods.characterData(name, -1, true);
|
||||
const r = await this.profileCache.register(c);
|
||||
|
||||
this.updateAdScoringForProfile(c, r.match.matchScore);
|
||||
this.updateAdScoringForProfile(c, r.match.matchScore, r.match.isFiltered);
|
||||
|
||||
return c;
|
||||
} catch (err) {
|
||||
|
@ -122,7 +122,7 @@ export class CacheManager {
|
|||
}
|
||||
|
||||
|
||||
updateAdScoringForProfile(c: ComplexCharacter, score: number): void {
|
||||
updateAdScoringForProfile(c: ComplexCharacter, score: number, isFiltered: boolean): void {
|
||||
EventBus.$emit(
|
||||
'character-score',
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ export class CacheManager {
|
|||
}
|
||||
);
|
||||
|
||||
this.populateAllConversationsWithScore(c.character.name, score);
|
||||
this.populateAllConversationsWithScore(c.character.name, score, isFiltered);
|
||||
}
|
||||
|
||||
|
||||
|
@ -446,9 +446,10 @@ export class CacheManager {
|
|||
// }
|
||||
|
||||
msg.score = p.match.matchScore;
|
||||
msg.filterMatch = p.match.isFiltered;
|
||||
|
||||
if (populateAll) {
|
||||
this.populateAllConversationsWithScore(char.name, p.match.matchScore);
|
||||
this.populateAllConversationsWithScore(char.name, p.match.matchScore, p.match.isFiltered);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -457,7 +458,7 @@ export class CacheManager {
|
|||
|
||||
|
||||
// tslint:disable-next-line: prefer-function-over-method
|
||||
protected populateAllConversationsWithScore(characterName: string, score: number): void {
|
||||
public populateAllConversationsWithScore(characterName: string, score: number, isFiltered: boolean): void {
|
||||
_.each(
|
||||
core.conversations.channelConversations,
|
||||
(ch: ChannelConversation) => {
|
||||
|
@ -467,6 +468,7 @@ export class CacheManager {
|
|||
// console.log('Update score', score, ch.name, m.sender.name, m.text, m.id);
|
||||
|
||||
m.score = score;
|
||||
m.filterMatch = isFiltered;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
import _ from 'lodash';
|
||||
import { Matcher } from '../matcher';
|
||||
import { BodyType, Build, Kink, Species, TagId } from '../matcher-types';
|
||||
import { SmartFilterSelection, SmartFilterSettings } from './types';
|
||||
import { Character } from '../../interfaces';
|
||||
import log from 'electron-log';
|
||||
import core from '../../chat/core';
|
||||
|
||||
export interface SmartFilterOpts {
|
||||
name: string;
|
||||
kinks?: Kink[],
|
||||
bodyTypes?: BodyType[],
|
||||
builds?: Build[],
|
||||
species?: Species[]
|
||||
isAnthro?: boolean;
|
||||
isHuman?: boolean;
|
||||
}
|
||||
|
||||
export class SmartFilter {
|
||||
constructor(private opts: SmartFilterOpts) {}
|
||||
|
||||
test(c: Character): boolean {
|
||||
const builds = this.testBuilds(c);
|
||||
const bodyTypes = this.testBodyTypes(c);
|
||||
const species = this.testSpecies(c);
|
||||
const isAnthro = this.testIsAnthro(c);
|
||||
const isHuman = this.testIsHuman(c);
|
||||
const kinks = this.testKinks(c);
|
||||
|
||||
const result = builds || bodyTypes || species || isAnthro || isHuman || kinks;
|
||||
|
||||
log.debug('smart-filter.test',
|
||||
{ name: c.name, filterName: this.opts.name, result, builds, bodyTypes, species, isAnthro, isHuman, kinks });
|
||||
|
||||
return this.testBuilds(c) || this.testBodyTypes(c) || this.testSpecies(c) || this.testIsAnthro(c) || this.testIsHuman(c) ||
|
||||
this.testKinks(c);
|
||||
}
|
||||
|
||||
testKinks(c: Character): boolean {
|
||||
if (!this.opts.kinks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const score = _.reduce(this.opts.kinks, (curScore, kinkId) => {
|
||||
const pref = Matcher.getKinkPreference(c, kinkId);
|
||||
|
||||
if (pref) {
|
||||
curScore.matches += 1;
|
||||
curScore.score += pref;
|
||||
}
|
||||
|
||||
return curScore;
|
||||
}, { score: 0, matches: 0 });
|
||||
|
||||
return score.matches >= 1 && score.score >= 1.0 + (Math.log((this.opts.kinks?.length || 0) + 1) / 2);
|
||||
}
|
||||
|
||||
testBuilds(c: Character): boolean {
|
||||
if (!this.opts.builds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const build = Matcher.getTagValueList(TagId.Build, c);
|
||||
|
||||
return !!build && !!_.find(this.opts.builds || [], build);
|
||||
}
|
||||
|
||||
testBodyTypes(c: Character): boolean {
|
||||
if (!this.opts.bodyTypes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bodyType = Matcher.getTagValueList(TagId.BodyType, c);
|
||||
|
||||
return !!bodyType && !!_.find(this.opts.bodyTypes || [], bodyType);
|
||||
}
|
||||
|
||||
testSpecies(c: Character): boolean {
|
||||
if (!this.opts.species) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const species = Matcher.species(c);
|
||||
|
||||
return !!species && !!_.find(this.opts.species || [], species);
|
||||
}
|
||||
|
||||
testIsHuman(c: Character): boolean {
|
||||
return !!this.opts.isHuman && (Matcher.isHuman(c) || false);
|
||||
}
|
||||
|
||||
testIsAnthro(c: Character): boolean {
|
||||
return !!this.opts.isAnthro && (Matcher.isAnthro(c) || false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type SmartFilterCollection = {
|
||||
[key in keyof SmartFilterSelection]: SmartFilter;
|
||||
};
|
||||
|
||||
export const smartFilters: SmartFilterCollection = {
|
||||
ageplay: new SmartFilter({
|
||||
name: 'ageplay',
|
||||
kinks: [Kink.Ageplay, Kink.AgeProgression, Kink.AgeRegression, Kink.UnderageCharacters, Kink.Infantilism]
|
||||
}),
|
||||
|
||||
anthro: new SmartFilter({
|
||||
name: 'anthro',
|
||||
isAnthro: true
|
||||
}),
|
||||
|
||||
feral: new SmartFilter({
|
||||
name: 'feral',
|
||||
bodyTypes: [BodyType.Feral]
|
||||
}),
|
||||
|
||||
gore: new SmartFilter({
|
||||
name: 'gore',
|
||||
kinks: [
|
||||
Kink.Abrasions, Kink.Castration, Kink.Death, Kink.Emasculation, Kink.ExecutionMurder, Kink.Gore, Kink.Impalement, Kink.Mutilation,
|
||||
Kink.Necrophilia, Kink.NonsexualPain, Kink.NonsexualTorture, Kink.Nullification, Kink.ToothRemoval, Kink.WoundFucking,
|
||||
Kink.Cannibalism, Kink.GenitalTorture
|
||||
]
|
||||
}),
|
||||
|
||||
human: new SmartFilter({
|
||||
name: 'human',
|
||||
isHuman: true
|
||||
}),
|
||||
|
||||
hyper: new SmartFilter({
|
||||
name: 'kinks',
|
||||
kinks: [Kink.HyperAsses, Kink.HyperBalls, Kink.HyperBreasts, Kink.HyperCocks, Kink.HyperFat, Kink.HyperMuscle, Kink.HyperVaginas,
|
||||
Kink.HyperVoluptous, Kink.HyperMuscleGrowth, Kink.MacroAsses, Kink.MacroBalls, Kink.MacroBreasts, Kink.MacroCocks]
|
||||
}),
|
||||
|
||||
incest: new SmartFilter({
|
||||
name: 'incest',
|
||||
kinks: [Kink.Incest, Kink.IncestParental, Kink.IncestSiblings, Kink.ParentChildPlay, Kink.ForcedIncest]
|
||||
}),
|
||||
|
||||
microMacro: new SmartFilter({
|
||||
name: 'microMacro',
|
||||
kinks: [Kink.MacroAsses, Kink.MacroBalls, Kink.MacroBreasts, Kink.MacroCocks, Kink.Macrophilia, Kink.MegaMacro, Kink.Microphilia,
|
||||
Kink.GrowthMacro, Kink.ShrinkingMicro, Kink.SizeDifferencesMicroMacro]
|
||||
}),
|
||||
|
||||
obesity: new SmartFilter({
|
||||
name: 'obesity',
|
||||
builds: [Build.Tubby, Build.Obese, Build.Chubby]
|
||||
}),
|
||||
|
||||
pregnancy: new SmartFilter({
|
||||
name: 'pregnancy',
|
||||
kinks: [Kink.AlternativePregnancy, Kink.AnalPregnancy, Kink.Birthing, Kink.ExtremePregnancy, Kink.MalePregnancy, Kink.Pregnancy]
|
||||
}),
|
||||
|
||||
pokemon: new SmartFilter({
|
||||
name: 'pokemon',
|
||||
species: [Species.Pokemon]
|
||||
}),
|
||||
|
||||
rape: new SmartFilter({
|
||||
name: 'rape',
|
||||
kinks: [Kink.PseudoRape, Kink.DubConsensual, Kink.Nonconsensual]
|
||||
}),
|
||||
|
||||
scat: new SmartFilter({
|
||||
name: 'scat',
|
||||
kinks: [Kink.HyperScat, Kink.Scat, Kink.ScatTorture, Kink.Soiling, Kink.SwallowingFeces]
|
||||
}),
|
||||
|
||||
std: new SmartFilter({
|
||||
name: 'std',
|
||||
kinks: [Kink.STDs]
|
||||
}),
|
||||
|
||||
taur: new SmartFilter({
|
||||
name: 'taur',
|
||||
bodyTypes: [BodyType.Taur]
|
||||
}),
|
||||
|
||||
unclean: new SmartFilter({
|
||||
name: 'unclean',
|
||||
kinks: [Kink.BelchingBurping, Kink.DirtyFeet, Kink.ExtremeMusk, Kink.Farting, Kink.Filth, Kink.Slob, Kink.Smegma, Kink.SwallowingVomit,
|
||||
Kink.UnwashedMusk, Kink.Vomiting]
|
||||
}),
|
||||
|
||||
vore: new SmartFilter({
|
||||
name: 'vore',
|
||||
kinks: [Kink.Absorption, Kink.AlternativeVore, Kink.AnalVore, Kink.Cannibalism, Kink.CockVore, Kink.CookingVore, Kink.Digestion,
|
||||
Kink.Disposal, Kink.HardVore, Kink.RealisticVore, Kink.SoftVore, Kink.Unbirthing, Kink.UnrealisticVore, Kink.VoreBeingPredator,
|
||||
Kink.VoreBeingPrey]
|
||||
}),
|
||||
|
||||
watersports: new SmartFilter({
|
||||
name: 'watersports',
|
||||
kinks: [Kink.HyperWatersports, Kink.PissEnemas, Kink.SwallowingUrine, Kink.Watersports, Kink.Wetting]
|
||||
}),
|
||||
|
||||
zoophilia: new SmartFilter({
|
||||
name: 'zoophilia',
|
||||
kinks: [Kink.Zoophilia, Kink.AnimalsFerals, Kink.Quadrupeds]
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
export function matchesSmartFilters(c: Character, opts: SmartFilterSettings): boolean {
|
||||
if (c.name === core.characters.ownCharacter.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (core.characters.get(c.name)?.isChatOp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.exceptionNames.includes(c.name)) {
|
||||
log.debug('smart-filter.exception', { name: c.name });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.minAge !== null || opts.maxAge !== null) {
|
||||
const age = Matcher.age(c);
|
||||
|
||||
if (age !== null) {
|
||||
if ((opts.minAge !== null && age < opts.minAge) || (opts.maxAge !== null && age > opts.maxAge)) {
|
||||
log.debug('smart-filter.age', { name: c.name, age, minAge: opts.minAge, maxAge: opts.maxAge });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !_.every(opts.smartFilters, (fs, filterName) => !fs || !(smartFilters as any)[filterName].test(c));
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// <!-- [Automated message] Sorry, the player of this character has indicated that they are not interested in characters matching your profile.-->
|
||||
// <!-- Need a filter for yourself? Try out [F-Chat Rising](https://mrstallion.github.io/fchat-rising/)!-->
|
||||
|
||||
|
||||
export const smartFilterTypes = {
|
||||
ageplay: { name: 'Ageplay' },
|
||||
anthro: { name: 'Anthros' },
|
||||
feral: { name: 'Ferals' },
|
||||
gore: { name: 'Gore/torture/death' },
|
||||
human: { name: 'Humans' },
|
||||
hyper: { name: 'Hyper' },
|
||||
incest: { name: 'Incest' },
|
||||
microMacro: { name: 'Micro/macro' },
|
||||
obesity: { name: 'Obesity' },
|
||||
pokemon: { name: 'Pokemons/Digimons' },
|
||||
pregnancy: { name: 'Pregnancy' },
|
||||
rape: { name: 'Rape' },
|
||||
scat: { name: 'Scat' },
|
||||
std: { name: 'STDs' },
|
||||
taur: { name: 'Taurs' },
|
||||
vore: { name: 'Vore and unbirthing' },
|
||||
unclean: { name: 'Unclean' },
|
||||
watersports: { name: 'Watersports' },
|
||||
zoophilia: { name: 'Zoophilia' }
|
||||
};
|
||||
|
||||
export type SmartFilterSelection = {
|
||||
[key in keyof typeof smartFilterTypes]: boolean;
|
||||
};
|
||||
|
||||
export interface SmartFilterSettings {
|
||||
hideAds: boolean;
|
||||
hideSearchResults: boolean;
|
||||
hideChannelMembers: boolean;
|
||||
hidePublicChannelMessages: boolean;
|
||||
hidePrivateChannelMessages: boolean;
|
||||
hidePrivateMessages: boolean;
|
||||
penalizeMatches: boolean;
|
||||
autoReply: boolean;
|
||||
|
||||
minAge: number | null;
|
||||
maxAge: number | null;
|
||||
|
||||
smartFilters: SmartFilterSelection;
|
||||
|
||||
exceptionNames: string[];
|
||||
}
|
|
@ -87,6 +87,22 @@ export enum BodyType {
|
|||
Taur = 145
|
||||
}
|
||||
|
||||
export enum Build {
|
||||
Lithe = 12,
|
||||
Thin = 14,
|
||||
Slim = 15,
|
||||
Average = 16,
|
||||
Toned = 17,
|
||||
Muscular = 18,
|
||||
Buff = 19,
|
||||
Herculean = 20,
|
||||
Tubby = 21,
|
||||
Obese = 22,
|
||||
Curvy = 129,
|
||||
Chubby = 200,
|
||||
Varies = 201
|
||||
}
|
||||
|
||||
export enum KinkPreference {
|
||||
Favorite = 1,
|
||||
Yes = 0.5,
|
||||
|
@ -113,7 +129,119 @@ export enum Kink {
|
|||
AnthroCharacters = 587,
|
||||
Humans = 609,
|
||||
|
||||
Mammals = 224
|
||||
Mammals = 224,
|
||||
|
||||
Abrasions = 1,
|
||||
Bloodplay = 4,
|
||||
Branding = 492,
|
||||
BreastNippleTorture = 36,
|
||||
Burning = 21,
|
||||
Castration = 20,
|
||||
Death = 28,
|
||||
Emasculation = 508,
|
||||
ExecutionMurder = 717,
|
||||
GenitalTorture = 276,
|
||||
Gore = 689,
|
||||
Impalement = 270,
|
||||
Menses = 99,
|
||||
Mutilation = 96,
|
||||
Necrophilia = 308,
|
||||
NonsexualPain = 486,
|
||||
NonsexualTorture = 103,
|
||||
Nullification = 334,
|
||||
Piercing = 479,
|
||||
SexualTorture = 174,
|
||||
SwallowingBlood = 202,
|
||||
ToothRemoval = 690,
|
||||
WoundFucking = 691,
|
||||
|
||||
HyperScat = 415,
|
||||
Scat= 164,
|
||||
ScatTorture = 369,
|
||||
Soiling = 509,
|
||||
SwallowingFeces = 201,
|
||||
|
||||
HyperWatersports = 414,
|
||||
PissEnemas = 533,
|
||||
SwallowingUrine = 203,
|
||||
Watersports = 59,
|
||||
Wetting = 371,
|
||||
|
||||
BelchingBurping = 709,
|
||||
DirtyFeet = 706,
|
||||
ExtremeMusk = 335,
|
||||
Farting = 549,
|
||||
Filth = 707,
|
||||
Messy = 89,
|
||||
Slob = 570,
|
||||
Smegma = 708,
|
||||
SwallowingVomit = 560,
|
||||
UnwashedMusk = 705,
|
||||
Vomiting = 184,
|
||||
|
||||
Absorption = 239,
|
||||
AlternativeVore = 244,
|
||||
AnalVore = 209,
|
||||
Cannibalism = 714,
|
||||
CockVore = 208,
|
||||
CookingVore = 716,
|
||||
Digestion = 238,
|
||||
Disposal = 241,
|
||||
HardVore = 66,
|
||||
RealisticVore = 242,
|
||||
SoftVore = 73,
|
||||
Unbirthing = 210,
|
||||
UnrealisticVore = 243,
|
||||
VoreBeingPredator = 422,
|
||||
VoreBeingPrey = 423,
|
||||
|
||||
AlternativePregnancy = 702,
|
||||
AnalPregnancy = 704,
|
||||
Birthing = 461,
|
||||
ExtremePregnancy = 272,
|
||||
MalePregnancy = 198,
|
||||
Pregnancy = 154,
|
||||
|
||||
STDs = 656,
|
||||
|
||||
PseudoRape = 522,
|
||||
DubConsensual = 657,
|
||||
Nonconsensual = 100,
|
||||
|
||||
Incest = 127,
|
||||
IncestParental = 646,
|
||||
IncestSiblings = 647,
|
||||
ParentChildPlay = 304,
|
||||
ForcedIncest = 53,
|
||||
|
||||
AgeProgression = 622,
|
||||
AgeRegression = 621,
|
||||
Infantilism = 497,
|
||||
|
||||
Zoophilia = 218,
|
||||
AnimalsFerals = 487,
|
||||
Quadrupeds = 382,
|
||||
|
||||
HyperAsses = 595,
|
||||
HyperBalls = 233,
|
||||
HyperBreasts = 594,
|
||||
HyperCocks = 60,
|
||||
HyperFat = 377,
|
||||
HyperMuscle = 376,
|
||||
HyperVaginas = 593,
|
||||
HyperVoluptous = 378,
|
||||
HyperMuscleGrowth = 389,
|
||||
|
||||
MacroAsses = 596,
|
||||
MacroBalls = 550,
|
||||
MacroBreasts = 91,
|
||||
MacroCocks = 61,
|
||||
Macrophilia = 285,
|
||||
MegaMacro = 374,
|
||||
Microphilia = 286,
|
||||
SizeDifferencesMicroMacro = 502,
|
||||
GrowthMacro = 384,
|
||||
ShrinkingMicro = 387
|
||||
}
|
||||
|
||||
export enum FurryPreference {
|
||||
|
|
|
@ -1235,9 +1235,17 @@ export class Matcher {
|
|||
+ _.values(m.them.scores).length;
|
||||
}
|
||||
|
||||
static age(c: Character): number | null {
|
||||
const rawAge = Matcher.getTagValue(TagId.Age, c);
|
||||
const age = ((rawAge) && (rawAge.string)) ? parseInt(rawAge.string, 10) : null;
|
||||
|
||||
return age && !Number.isNaN(age) && Number.isFinite(age) ? age : null;
|
||||
}
|
||||
|
||||
static calculateSearchScoreForMatch(
|
||||
score: Scoring,
|
||||
match: MatchReport
|
||||
match: MatchReport,
|
||||
penalty: number
|
||||
): number {
|
||||
const totalScoreDimensions = match ? Matcher.countScoresTotal(match) : 0;
|
||||
const dimensionsAtScoreLevel = match ? (Matcher.countScoresAtLevel(match, score) || 0) : 0;
|
||||
|
@ -1289,10 +1297,11 @@ export class Matcher {
|
|||
dimensionsAboveScoreLevel,
|
||||
dimensionsAtScoreLevel,
|
||||
theirAtLevelDimensions,
|
||||
theirAboveLevelDimensions
|
||||
theirAboveLevelDimensions,
|
||||
penalty
|
||||
}
|
||||
);
|
||||
|
||||
return (atLevelScore + aboveLevelScore);
|
||||
return (atLevelScore + aboveLevelScore + penalty);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Matcher, MatchReport } from './matcher';
|
|||
import { PermanentIndexedStore } from './store/types';
|
||||
import { CharacterImage, SimpleCharacter } from '../interfaces';
|
||||
import { Scoring } from './matcher-types';
|
||||
import { matchesSmartFilters } from './filter/smart-filter';
|
||||
|
||||
|
||||
export interface MetaRecord {
|
||||
|
@ -30,6 +31,8 @@ export interface CharacterMatchSummary {
|
|||
// dimensionsAboveScoreLevel: number;
|
||||
// totalScoreDimensions: number;
|
||||
searchScore: number;
|
||||
isFiltered: boolean;
|
||||
autoResponded?: boolean;
|
||||
}
|
||||
|
||||
export interface CharacterCacheRecord {
|
||||
|
@ -52,6 +55,11 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
}
|
||||
|
||||
|
||||
onEachInMemory(cb: (c: CharacterCacheRecord, key: string) => void): void {
|
||||
_.each(this.cache, cb);
|
||||
}
|
||||
|
||||
|
||||
getSync(name: string): CharacterCacheRecord | null {
|
||||
const key = AsyncCache.nameKey(name);
|
||||
|
||||
|
@ -153,8 +161,13 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
|||
// 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 };
|
||||
const isFiltered = matchesSmartFilters(c.character, core.state.settings.risingFilter);
|
||||
|
||||
const searchScore = match
|
||||
? Matcher.calculateSearchScoreForMatch(score, match, isFiltered && core.state.settings.risingFilter.penalizeMatches ? -2 : 0)
|
||||
: 0;
|
||||
|
||||
const matchDetails = { matchScore: score, searchScore, isFiltered };
|
||||
|
||||
if ((this.store) && (!skipStore)) {
|
||||
await this.store.storeProfile(c);
|
||||
|
|
Loading…
Reference in New Issue