Latest messages in character preview; better filters; log filtered messages
This commit is contained in:
parent
f160b0f176
commit
75b5ef54cf
|
@ -1,9 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## 1.18.0
|
||||
* Upgraded to Electron 18
|
||||
* Upgraded to Electron 17
|
||||
* Fixed MacOS M1 incompatibilities
|
||||
* Improved age detection
|
||||
* Taur and feral body types are now matched against kink preferences
|
||||
* Filtered messages are now accessible in the conversation history
|
||||
* Rejection messages are now also sent to filtered characters whose profiles have not been scored at the time they message you
|
||||
* Slightly relaxed filter scoring
|
||||
* Character preview now shows last messages from conversation history
|
||||
|
||||
## 1.17.1
|
||||
* Fixes to smart filters
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Download
|
||||
[Windows](https://github.com/mrstallion/fchat-rising/releases/download/v1.17.1/F-Chat-Rising-1.17.1-win.exe) (82 MB)
|
||||
| [MacOS Intel](https://github.com/mrstallion/fchat-rising/releases/download/v1.17.1/F-Chat-Rising-1.17.1-macos-intel.dmg) (82 MB)
|
||||
| [MacOS M1](https://github.com/mrstallion/fchat-rising/releases/download/v1.17.1/F-Chat-Rising-1.17.1-macos-m1.dmg) (84 MB)
|
||||
| [Linux](https://github.com/mrstallion/fchat-rising/releases/download/v1.17.1/F-Chat-Rising-1.17.1-linux.AppImage) (82 MB)
|
||||
[Windows](https://github.com/mrstallion/fchat-rising/releases/download/v1.18.0/F-Chat-Rising-1.18.0-win.exe) (82 MB)
|
||||
| [MacOS Intel](https://github.com/mrstallion/fchat-rising/releases/download/v1.18.0/F-Chat-Rising-1.18.0-macos-intel.dmg) (82 MB)
|
||||
| [MacOS M1](https://github.com/mrstallion/fchat-rising/releases/download/v1.18.0/F-Chat-Rising-1.18.0-macos-m1.dmg) (84 MB)
|
||||
| [Linux](https://github.com/mrstallion/fchat-rising/releases/download/v1.18.0/F-Chat-Rising-1.18.0-linux.AppImage) (82 MB)
|
||||
|
||||
|
||||
# F-Chat Rising
|
||||
|
|
|
@ -618,8 +618,23 @@ function isOfInterest(this: any, character: Character): boolean {
|
|||
return character.isFriend || character.isBookmarked || state.privateMap[character.name.toLowerCase()] !== undefined;
|
||||
}
|
||||
|
||||
async function testSmartFilterForPrivateMessage(fromChar: Character.Character): Promise<boolean> {
|
||||
async function withNeutralVisibilityPrivateConversation(
|
||||
character: Character.Character,
|
||||
cb: (p: PrivateConversation, c: Character.Character) => Promise<void>
|
||||
): Promise<void> {
|
||||
const isVisibleConversation = !!(state.getPrivate as any)(character, true);
|
||||
const conv = state.getPrivate(character);
|
||||
|
||||
await cb(conv, character);
|
||||
|
||||
if (!isVisibleConversation) {
|
||||
await conv.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function testSmartFilterForPrivateMessage(fromChar: Character.Character, originalMessage?: Message): Promise<boolean> {
|
||||
const cachedProfile = core.cache.profileCache.getSync(fromChar.name) || await core.cache.profileCache.get(fromChar.name);
|
||||
const firstTime = cachedProfile && !cachedProfile.match.autoResponded;
|
||||
|
||||
if (
|
||||
cachedProfile &&
|
||||
|
@ -629,23 +644,51 @@ async function testSmartFilterForPrivateMessage(fromChar: Character.Character):
|
|||
) {
|
||||
cachedProfile.match.autoResponded = true;
|
||||
|
||||
log.debug('filter.autoresponse', { name: fromChar.name });
|
||||
|
||||
void Conversation.conversationThroat(
|
||||
async() => {
|
||||
log.debug('filter.autoresponse', { name: fromChar.name });
|
||||
|
||||
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]';
|
||||
// tslint:disable-next-line:prefer-template
|
||||
const message = {
|
||||
recipient: fromChar.name,
|
||||
message: '\n[sub][color=orange][b][AUTOMATED MESSAGE][/b][/color][/sub]\n' +
|
||||
'Sorry, the player of this character is not interested in characters matching your profile.' +
|
||||
`${core.state.settings.risingFilter.hidePrivateMessages ? ' They did not see your message. To bypass this warning, send your message again.' : ''}\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: fromChar.name, message: m});
|
||||
core.connection.send('PRI', message);
|
||||
core.cache.markLastPostTime();
|
||||
|
||||
if (core.state.settings.logMessages) {
|
||||
const logMessage = createMessage(Interfaces.Message.Type.Message, core.characters.ownCharacter,
|
||||
message.message, new Date());
|
||||
|
||||
await withNeutralVisibilityPrivateConversation(
|
||||
fromChar,
|
||||
async(p) => core.logs.logMessage(p, logMessage)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedProfile && cachedProfile.match.isFiltered && core.state.settings.risingFilter.hidePrivateMessages) {
|
||||
if (
|
||||
cachedProfile &&
|
||||
cachedProfile.match.isFiltered &&
|
||||
core.state.settings.risingFilter.hidePrivateMessages &&
|
||||
firstTime // subsequent messages bypass this filter on purpose
|
||||
) {
|
||||
if (core.state.settings.logMessages && originalMessage) {
|
||||
await withNeutralVisibilityPrivateConversation(
|
||||
fromChar,
|
||||
async(p) => core.logs.logMessage(p, originalMessage)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -738,7 +781,7 @@ export default function(this: any): Interfaces.State {
|
|||
if(char.isIgnored) return connection.send('IGN', {action: 'notify', character: data.character});
|
||||
const message = createMessage(MessageType.Message, char, decodeHTML(data.message), time);
|
||||
|
||||
if (await testSmartFilterForPrivateMessage(char) === true) {
|
||||
if (await testSmartFilterForPrivateMessage(char, message) === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,9 @@ export namespace Conversation {
|
|||
readonly selectedConversation: Conversation
|
||||
readonly hasNew: boolean;
|
||||
byKey(key: string): Conversation | undefined
|
||||
getPrivate(character: Character): PrivateConversation
|
||||
|
||||
getPrivate(character: Character): PrivateConversation;
|
||||
getPrivate(character: Character, noCreate: boolean): PrivateConversation | undefined;
|
||||
}
|
||||
|
||||
export enum Setting {
|
||||
|
|
|
@ -43,6 +43,15 @@
|
|||
<bbcode :text="statusMessage"></bbcode>
|
||||
</div>
|
||||
|
||||
<div class="conversation" v-if="conversation && conversation.length > 0">
|
||||
<h4>Latest Messages</h4>
|
||||
|
||||
<template v-for="message in conversation">
|
||||
<message-view :message="message" :key="message.id">
|
||||
</message-view>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="latest-ad-message" v-if="latestAd && (latestAd.message !== statusMessage)">
|
||||
<h4>Latest Ad <span class="message-time">{{formatTime(latestAd.datePosted)}}</span></h4>
|
||||
<bbcode :text="latestAd.message"></bbcode>
|
||||
|
@ -82,6 +91,8 @@ import { EventBus } from './event-bus';
|
|||
import { Character, CustomKink } from '../../interfaces';
|
||||
import { matchesSmartFilters, testSmartFilters } from '../../learn/filter/smart-filter';
|
||||
import { smartFilterTypes } from '../../learn/filter/types';
|
||||
import { Conversation } from '../interfaces';
|
||||
import MessageView from '../message_view';
|
||||
|
||||
interface CustomKinkWithScore extends CustomKink {
|
||||
score: number;
|
||||
|
@ -91,7 +102,8 @@ interface CustomKinkWithScore extends CustomKink {
|
|||
@Component({
|
||||
components: {
|
||||
'match-tags': MatchTags,
|
||||
bbcode: BBCodeView(core.bbCodeParser)
|
||||
bbcode: BBCodeView(core.bbCodeParser),
|
||||
'message-view': MessageView
|
||||
}
|
||||
})
|
||||
export default class CharacterPreview extends Vue {
|
||||
|
@ -132,6 +144,8 @@ export default class CharacterPreview extends Vue {
|
|||
scoreWatcher: ((event: any) => void) | null = null;
|
||||
customs?: CustomKinkWithScore[];
|
||||
|
||||
conversation?: Conversation.Message[];
|
||||
|
||||
|
||||
@Hook('mounted')
|
||||
mounted(): void {
|
||||
|
@ -189,6 +203,8 @@ export default class CharacterPreview extends Vue {
|
|||
this.customs = undefined;
|
||||
this.ownCharacter = core.characters.ownProfile;
|
||||
|
||||
this.conversation = undefined;
|
||||
|
||||
this.smartFilterIsFiltered = false;
|
||||
this.smartFilterDetails = [];
|
||||
|
||||
|
@ -199,6 +215,8 @@ export default class CharacterPreview extends Vue {
|
|||
this.character = await this.getCharacterData(characterName);
|
||||
this.match = Matcher.identifyBestMatchReport(this.ownCharacter!.character, this.character!.character);
|
||||
|
||||
void this.updateConversationStatus();
|
||||
|
||||
this.updateSmartFilterReport();
|
||||
this.updateCustoms();
|
||||
this.updateDetails();
|
||||
|
@ -229,6 +247,25 @@ export default class CharacterPreview extends Vue {
|
|||
];
|
||||
}
|
||||
|
||||
async updateConversationStatus(): Promise<void> {
|
||||
const char = core.characters.get(this.characterName!);
|
||||
|
||||
if (char) {
|
||||
const messages = await core.logs.getLogs(core.characters.ownCharacter.name, char.name.toLowerCase(), new Date());
|
||||
const matcher = /\[AUTOMATED MESSAGE]/;
|
||||
|
||||
this.conversation = _.map(
|
||||
_.takeRight(_.filter(messages, (m) => !matcher.exec(m.text)), 3),
|
||||
(m) => ({
|
||||
...m,
|
||||
text: m.text.length > 512 ? m.text.substr(0, 512) + '…' : m.text
|
||||
})
|
||||
);
|
||||
|
||||
// this.conversation = core.conversations.getPrivate(char, true);
|
||||
}
|
||||
}
|
||||
|
||||
updateOnlineStatus(): void {
|
||||
this.onlineCharacter = core.characters.get(this.characterName!);
|
||||
|
||||
|
@ -432,6 +469,7 @@ export default class CharacterPreview extends Vue {
|
|||
|
||||
.status-message,
|
||||
.latest-ad-message,
|
||||
.conversation,
|
||||
.filter-matches {
|
||||
display: block;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
|
|
|
@ -50,7 +50,7 @@ theme: jekyll-theme-slate
|
|||
changelog: https://github.com/mrstallion/fchat-rising/blob/master/CHANGELOG.md
|
||||
|
||||
download:
|
||||
version: 1.17.1
|
||||
version: 1.18.0
|
||||
|
||||
url: https://github.com/mrstallion/fchat-rising/releases/download/v%VERSION%/F-Chat-Rising-%VERSION%-%PLATFORM_TAIL%
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "1.17.1",
|
||||
"version": "1.18.0",
|
||||
"author": "The F-List Team and Mister Stallion (Esq.)",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
|
|
|
@ -21,7 +21,8 @@ import { PermanentIndexedStore } from './store/types';
|
|||
import * as path from 'path';
|
||||
// import * as electron from 'electron';
|
||||
|
||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
|
||||
import log from 'electron-log';
|
||||
import { testSmartFilterForPrivateMessage } from '../chat/conversations'; //tslint:disable-line:match-default-export-name
|
||||
|
||||
|
||||
export interface ProfileCacheQueueEntry {
|
||||
|
@ -132,8 +133,21 @@ export class CacheManager {
|
|||
);
|
||||
|
||||
this.populateAllConversationsWithScore(c.character.name, score, isFiltered);
|
||||
void this.respondToPendingRejections(c);
|
||||
}
|
||||
|
||||
// Manage rejections in case we didn't have a score at the time we received the message
|
||||
async respondToPendingRejections(c: ComplexCharacter): Promise<void> {
|
||||
const char = core.characters.get(c.character.name);
|
||||
|
||||
if (char && char.status !== 'offline') {
|
||||
const conv = core.conversations.getPrivate(char, true);
|
||||
|
||||
if (conv && conv.messages.length > 0 && Date.now() - _.last(conv.messages)!.time.getTime() < 3 * 60 * 1000) {
|
||||
await testSmartFilterForPrivateMessage(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addProfile(character: string | ComplexCharacter): Promise<void> {
|
||||
if (typeof character === 'string') {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import _ from 'lodash';
|
||||
import * as _ from 'lodash';
|
||||
import { Matcher } from '../matcher';
|
||||
import { BodyType, Build, Gender, Kink, Species, TagId } from '../matcher-types';
|
||||
import { SmartFilterSelection, SmartFilterSettings } from './types';
|
||||
|
@ -28,6 +28,10 @@ export interface SmartFilterTestResult {
|
|||
kinks: boolean;
|
||||
}
|
||||
|
||||
function getBaseLog(base: number, x: number): number {
|
||||
return Math.log(x) / Math.log(base);
|
||||
}
|
||||
|
||||
export class SmartFilter {
|
||||
constructor(private opts: SmartFilterOpts) {}
|
||||
|
||||
|
@ -64,7 +68,10 @@ export class SmartFilter {
|
|||
return curScore;
|
||||
}, { score: 0, matches: 0 });
|
||||
|
||||
return score.matches >= 1 && score.score >= 1.0 + (Math.log((this.opts.kinks?.length || 0) + 1) / 2);
|
||||
const baseLog = getBaseLog(5, (this.opts.kinks?.length || 0) + 1);
|
||||
const threshold = (baseLog * baseLog) + 1;
|
||||
|
||||
return score.matches >= 1 && score.score >= threshold;
|
||||
}
|
||||
|
||||
testBuilds(c: Character): boolean {
|
||||
|
|
|
@ -241,7 +241,9 @@ export enum Kink {
|
|||
Microphilia = 286,
|
||||
SizeDifferencesMicroMacro = 502,
|
||||
GrowthMacro = 384,
|
||||
ShrinkingMicro = 387
|
||||
ShrinkingMicro = 387,
|
||||
|
||||
Taurs = 68
|
||||
}
|
||||
|
||||
export enum FurryPreference {
|
||||
|
@ -341,6 +343,16 @@ export const genderKinkMapping: GenderKinkIdMap = {
|
|||
[Gender.Transgender]: Kink.Transgenders
|
||||
};
|
||||
|
||||
|
||||
export interface BodyTypeKinkIdMap {
|
||||
[key: number]: Kink
|
||||
}
|
||||
|
||||
export const bodyTypeKinkMapping: BodyTypeKinkIdMap = {
|
||||
[BodyType.Feral]: Kink.AnimalsFerals,
|
||||
[BodyType.Taur]: Kink.Taurs
|
||||
};
|
||||
|
||||
// if no species and 'no furry characters', === human
|
||||
// if no species and dislike 'anthro characters' === human
|
||||
|
||||
|
@ -468,7 +480,7 @@ export const speciesMapping: SpeciesMap = {
|
|||
],
|
||||
|
||||
[Species.Human]: ['human', 'homo sapiens', 'human.*', 'homo[ -]?sapi[ea]ns?', 'woman', 'hy?[uo]+m[aie]n', 'humaine?',
|
||||
'meat[ -]?popsicle',
|
||||
'meat[ -]?popsicle'
|
||||
],
|
||||
|
||||
[Species.Elf]: ['drow', 'draenei', 'dunmer', 'draenai', 'blutelf[e]?', 'elf.*', 'drow.*', 'e[ -]l[ -]f', 'sin\'?dorei',
|
||||
|
|
|
@ -10,7 +10,7 @@ import anyAscii from 'any-ascii';
|
|||
import { Store } from '../site/character_page/data_store';
|
||||
|
||||
import {
|
||||
BodyType,
|
||||
BodyType, bodyTypeKinkMapping,
|
||||
fchatGenderMap,
|
||||
FurryPreference,
|
||||
Gender,
|
||||
|
@ -151,6 +151,7 @@ export class CharacterAnalysis {
|
|||
readonly subDomRole: SubDomRole | null;
|
||||
readonly position: Position | null;
|
||||
readonly postLengthPreference: PostLengthPreference | null;
|
||||
readonly bodyType: BodyType | null;
|
||||
|
||||
readonly isAnthro: boolean | null;
|
||||
readonly isHuman: boolean | null;
|
||||
|
@ -166,10 +167,9 @@ export class CharacterAnalysis {
|
|||
this.subDomRole = Matcher.getTagValueList(TagId.SubDomRole, c);
|
||||
this.position = Matcher.getTagValueList(TagId.Position, c);
|
||||
this.postLengthPreference = Matcher.getTagValueList(TagId.PostLength, c);
|
||||
this.bodyType = Matcher.getTagValueList(TagId.BodyType, c);
|
||||
|
||||
const ageTag = Matcher.getTagValue(TagId.Age, c);
|
||||
|
||||
this.age = ((ageTag) && (ageTag.string)) ? parseInt(ageTag.string, 10) : null;
|
||||
this.age = Matcher.age(c);
|
||||
|
||||
this.isAnthro = Matcher.isAnthro(c);
|
||||
this.isHuman = Matcher.isHuman(c);
|
||||
|
@ -405,7 +405,8 @@ export class Matcher {
|
|||
[TagId.SubDomRole]: this.resolveSubDomScore(),
|
||||
[TagId.Kinks]: this.resolveKinkScore(pronoun),
|
||||
[TagId.PostLength]: this.resolvePostLengthScore(),
|
||||
[TagId.Position]: this.resolvePositionScore()
|
||||
[TagId.Position]: this.resolvePositionScore(),
|
||||
[TagId.BodyType]: this.resolveBodyTypeScore()
|
||||
},
|
||||
|
||||
info: {
|
||||
|
@ -723,6 +724,19 @@ export class Matcher {
|
|||
return new Score(Scoring.NEUTRAL);
|
||||
}
|
||||
|
||||
private resolveBodyTypeScore(): Score {
|
||||
const theirBodyType = Matcher.getTagValueList(TagId.BodyType, this.them);
|
||||
|
||||
if (theirBodyType && theirBodyType in bodyTypeKinkMapping) {
|
||||
const bodyTypePreference = Matcher.getKinkPreference(this.you, bodyTypeKinkMapping[theirBodyType]);
|
||||
|
||||
if (bodyTypePreference !== null) {
|
||||
return Matcher.formatKinkScore(bodyTypePreference, `{BodyType[theirBodyType].toLowerCase()}s`);
|
||||
}
|
||||
}
|
||||
|
||||
return new Score(Scoring.NEUTRAL);
|
||||
}
|
||||
|
||||
private resolveSubDomScore(): Score {
|
||||
const you = this.you;
|
||||
|
@ -954,7 +968,6 @@ export class Matcher {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
// private countKinksByBucket(kinks: { [key: number]: KinkChoice }): { favorite: number, yes: number, maybe: number, no: number } {
|
||||
// return _.reduce(
|
||||
// kinks,
|
||||
|
@ -971,7 +984,6 @@ export class Matcher {
|
|||
// );
|
||||
// }
|
||||
|
||||
|
||||
private getAllStandardKinks(c: Character): { [key: number]: KinkChoice } {
|
||||
const kinks = _.pickBy(c.kinks, _.isString);
|
||||
|
||||
|
@ -1244,7 +1256,8 @@ export class Matcher {
|
|||
|
||||
const ageStr = rawAge.string.toLowerCase().replace(/[,.]/g, '').trim();
|
||||
|
||||
if ((ageStr.indexOf('shota') >= 0) || (ageStr.indexOf('loli') >= 0) || (ageStr.indexOf('lolli') >= 0) || (ageStr.indexOf('pup') >= 0)) {
|
||||
if ((ageStr.indexOf('shota') >= 0) || (ageStr.indexOf('loli') >= 0)
|
||||
|| (ageStr.indexOf('lolli') >= 0) || (ageStr.indexOf('pup') >= 0)) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
|
@ -1297,7 +1310,8 @@ export class Matcher {
|
|||
return { min: Math.min(v1, v2), max: Math.max(v1, v2) };
|
||||
}
|
||||
|
||||
if ((ageStr.indexOf('shota') >= 0) || (ageStr.indexOf('loli') >= 0) || (ageStr.indexOf('lolli') >= 0) || (ageStr.indexOf('pup') >= 0)) {
|
||||
if ((ageStr.indexOf('shota') >= 0) || (ageStr.indexOf('loli') >= 0)
|
||||
|| (ageStr.indexOf('lolli') >= 0) || (ageStr.indexOf('pup') >= 0)) {
|
||||
return { min: 10, max: 10 };
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "f-list-rising",
|
||||
"version": "1.17.1",
|
||||
"version": "1.18.0",
|
||||
"author": "The F-List Team and and Mister Stallion (Esq.)",
|
||||
"description": "A heavily modded F-Chat 3.0 client for F-List",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
}
|
||||
|
||||
yourInterestIsRelevant(id: number): boolean {
|
||||
return ((id === TagId.Gender) || (id === TagId.Age) || (id === TagId.Species));
|
||||
return ((id === TagId.Gender) || (id === TagId.Age) || (id === TagId.Species) || (id === TagId.BodyType));
|
||||
}
|
||||
|
||||
get contactLink(): string | undefined {
|
||||
|
|
Loading…
Reference in New Issue