diff --git a/chat/ImagePreview.vue b/chat/ImagePreview.vue index 0c3fd3c..85efc21 100644 --- a/chat/ImagePreview.vue +++ b/chat/ImagePreview.vue @@ -49,6 +49,10 @@ [url=https://www.sex.com/pin/38152484-she-likes-it-rough/]Test[/url] [url=https://www.sex.com/pin/57537179-cock-slapping-hungry-tongue/]Test[/url] + + [url=https://imgur.com/gallery/ILsb94I]Imgur gallery[/url] + + [url=https://imgur.com/CIKv6sA]Imgur image[/url] */ import * as _ from 'lodash'; @@ -57,6 +61,7 @@ import { EventBus, EventBusEvent } from './event-bus'; import {domain} from '../bbcode/core'; import {ImagePreviewMutator} from './image-preview-mutator'; + import {ImageUrlMutator} from './image-url-mutator'; import {Point, screen, WebviewTag} from 'electron'; import Timer = NodeJS.Timer; @@ -76,13 +81,15 @@ protected visible = false; protected url: string | null = 'about:blank'; protected parent: ImagePreview; + protected debug: boolean; - abstract show(url: string): void; + abstract show(url: string): Promise; abstract hide(): void; abstract match(domainName: string): boolean; constructor(parent: ImagePreview) { this.parent = parent; + this.debug = parent.debug; } isVisible(): boolean { @@ -92,6 +99,10 @@ getUrl(): string | null { return this.url; } + + setDebug(debug: boolean): void { + this.debug = debug; + } } @@ -102,7 +113,7 @@ } - show(url: string): void { + async show(url: string): Promise { this.visible = true; this.url = url; } @@ -119,6 +130,9 @@ protected allowCachedUrl = true; + protected urlMutator = new ImageUrlMutator(this.parent.debug); + + hide(): void { const wasVisible = this.visible; @@ -139,20 +153,27 @@ } - show(url: string): void { + setDebug(debug: boolean): void { + this.debug = debug; + + this.urlMutator.setDebug(debug); + } + + + async show(url: string): Promise { const webview = this.parent.getWebview(); try { if ((this.allowCachedUrl) && ((webview.getURL() === url) || (url === this.lastExternalUrl))) { - if (this.parent.debug) + if (this.debug) console.log('ImagePreview: exec re-show mutator'); webview.executeJavaScript(this.parent.jsMutator.getReShowMutator()); } else { - if (this.parent.debug) + if (this.debug) console.log('ImagePreview: must load; skip re-show because urls don\'t match', this.url, webview.getURL()); - webview.loadURL(url); + webview.loadURL(await this.urlMutator.resolve(url)); } } catch (err) { @@ -520,6 +541,8 @@ this.debug = !this.debug; this.jsMutator.setDebug(this.debug); + this.localPreviewHelper.setDebug(this.debug); + this.externalPreviewHelper.setDebug(this.debug); if (this.debug) { const webview = this.getWebview(); diff --git a/chat/image-url-mutator.ts b/chat/image-url-mutator.ts new file mode 100644 index 0000000..2450fc7 --- /dev/null +++ b/chat/image-url-mutator.ts @@ -0,0 +1,116 @@ +import * as _ from 'lodash'; +import Axios from 'axios'; + +export type UrlSolverCallback = (url: string, match: RegExpMatchArray) => Promise; + +export interface UrlSolver { + matcher: RegExp; + solver: UrlSolverCallback; +} + + +export class ImageUrlMutator { + + private solvers: UrlSolver[] = []; + + private static IMGUR_CLIENT_ID = 'd60e27140a73b2e'; + + private debug: boolean; + + + constructor(debug: boolean) { + this.debug = debug; + + this.init(); + } + + protected init(): void { + this.add( + /^https?:\/\/imgur.com\/gallery\/([a-zA-Z0-9]+)/, + async(url: string, match: RegExpMatchArray): Promise => { + // Imgur Gallery + const galleryId = match[1]; + + try { + const result = await Axios.get( + `https://api.imgur.com/3/gallery/${galleryId}/images`, + { + headers: { + Authorization: `Client-ID ${ImageUrlMutator.IMGUR_CLIENT_ID}` + } + } + ); + + const imageUrl = _.get(result, 'data.data.0.link', null); + + if (!imageUrl) { + return url; + } + + const imageCount = _.get(result, 'data.data.length', 1); + + if (this.debug) + console.log('Imgur gallery', url, imageUrl, imageCount); + + return `${imageUrl}?flist_gallery_image_count=${imageCount}`; + + } catch (err) { + console.error('Imgur Gallery Failure', url, err); + return url; + } + } + ); + + // must be AFTER gallery test + this.add( + /^https?:\/\/imgur.com\/([a-zA-Z0-9]+)/, + async(url: string, match: RegExpMatchArray): Promise => { + // Single Imgur Image + const imageId = match[1]; + + try { + const result = await Axios.get( + `https://api.imgur.com/3/image/${imageId}`, + { + headers: { + Authorization: `Client-ID ${ImageUrlMutator.IMGUR_CLIENT_ID}` + } + } + ); + + const imageUrl = _.get(result, 'data.data.link', url); + + if (this.debug) + console.log('Imgur image', url, imageUrl); + + return imageUrl as string; + } catch (err) { + console.error('Imgur Image Failure', url, err); + return url; + } + } + ); + } + + + setDebug(debug: boolean): void { + this.debug = debug; + } + + + protected add(matcher: RegExp, solver: UrlSolverCallback): void { + this.solvers.push({matcher, solver}); + } + + + async resolve(url: string): Promise { + const match = _.find(this.solvers, (s: UrlSolver) => url.match(s.matcher)) as (UrlSolver | undefined); + + if (!match) { + return url; + } + + return match.solver(url, url.match(match.matcher) as RegExpMatchArray); + } +} + diff --git a/electron/Index.vue b/electron/Index.vue index 509fc99..5763ded 100644 --- a/electron/Index.vue +++ b/electron/Index.vue @@ -99,6 +99,25 @@ const webContents = electron.remote.getCurrentWebContents(); const parent = electron.remote.getCurrentWindow().webContents; + // Allow requests to imgur.com + const session = electron.remote.session; + + /* tslint:disable:no-unsafe-any no-any no-unnecessary-type-assertion */ + session!.defaultSession!.webRequest!.onBeforeSendHeaders( + { + urls: [ + 'https?://(api|i).imgur.com/*' + ] + }, + (details: any, callback: any) => { + details.requestHeaders['Origin'] = null; + details.headers['Origin'] = null; + + callback({requestHeaders: details.requestHeaders}); + } + ); + + log.info('About to load keytar'); /* tslint:disable: no-any no-unsafe-any */ //because this is hacky diff --git a/learn/matcher.ts b/learn/matcher.ts index 89390e0..a56aacf 100644 --- a/learn/matcher.ts +++ b/learn/matcher.ts @@ -9,7 +9,7 @@ export enum TagId { Gender = 3, Build = 13, FurryPreference = 29, - BdsmRole = 15, + SubDomRole = 15, Position = 41, BodyType = 51, ApparentAge = 64, @@ -29,6 +29,14 @@ export enum Gender { Shemale = 141 } +export enum SubDomRole { + AlwaysSubmissive = 7, + UsuallySubmissive = 8, + Switch = 9, + UsuallyDominant = 10, + AlwaysDominant = 11 +} + export enum Orientation { Straight = 4, Gay = 5, @@ -73,6 +81,8 @@ enum Kink { Ageplay = 196, UnderageCharacters = 207, + RoleReversal = 408, + AnthroCharacters = 587, Humans = 609, @@ -189,7 +199,7 @@ const fchatGenderMap: FchatGenderMap = { Shemale: Gender.Shemale, Herm: Gender.Herm, 'Male-Herm': Gender.MaleHerm, - 'Cunt-boy': Gender.Cuntboy, + 'Cunt-Boy': Gender.Cuntboy, Transgender: Gender.Transgender }; @@ -285,6 +295,7 @@ export class CharacterAnalysis { readonly species: Species | null; readonly furryPreference: FurryPreference | null; readonly age: number | null; + readonly subDomRole: SubDomRole | null; readonly isAnthro: boolean | null; readonly isHuman: boolean | null; @@ -297,6 +308,7 @@ export class CharacterAnalysis { this.orientation = Matcher.getTagValueList(TagId.Orientation, c); this.species = Matcher.species(c); this.furryPreference = Matcher.getTagValueList(TagId.FurryPreference, c); + this.subDomRole = Matcher.getTagValueList(TagId.SubDomRole, c); const ageTag = Matcher.getTagValue(TagId.Age, c); @@ -360,7 +372,8 @@ export class Matcher { [TagId.Gender]: this.resolveGenderScore(), [TagId.Age]: this.resolveAgeScore(), [TagId.FurryPreference]: this.resolveFurryPairingsScore(), - [TagId.Species]: this.resolveSpeciesScore() + [TagId.Species]: this.resolveSpeciesScore(), + [TagId.SubDomRole]: this.resolveSubDomScore() }, info: { @@ -610,7 +623,6 @@ export class Matcher { private resolveGenderScore(): Score { const you = this.you; - const theirGender = this.theirAnalysis.gender; if (theirGender === null) @@ -625,6 +637,72 @@ export class Matcher { return new Score(Scoring.NEUTRAL); } + + private resolveSubDomScore(): Score { + const you = this.you; + const yourSubDomRole = this.yourAnalysis.subDomRole; + const theirSubDomRole = this.theirAnalysis.subDomRole; + const yourRoleReversalPreference = Matcher.getKinkPreference(you, Kink.RoleReversal); + + if ((!yourSubDomRole) || (!theirSubDomRole)) + return new Score(Scoring.NEUTRAL); + + if ((yourSubDomRole === SubDomRole.AlwaysDominant) || (yourSubDomRole === SubDomRole.UsuallyDominant)) { + if (theirSubDomRole === SubDomRole.Switch) + return new Score(Scoring.WEAK_MATCH, `Likes switches`); + + if ((theirSubDomRole === SubDomRole.AlwaysSubmissive) || (theirSubDomRole === SubDomRole.UsuallySubmissive)) + return new Score(Scoring.MATCH, `Loves submissives`); + + if (yourRoleReversalPreference === KinkPreference.Favorite) + return new Score(Scoring.MATCH, `Loves role reversal`); + + if (yourRoleReversalPreference === KinkPreference.Yes) + return new Score(Scoring.MATCH, `Likes role reversal`); + + if ((yourSubDomRole === SubDomRole.AlwaysDominant) && (theirSubDomRole === SubDomRole.AlwaysDominant)) + return new Score(Scoring.MISMATCH, 'No dominants'); + + return new Score(Scoring.WEAK_MISMATCH, 'Hesitant on dominants'); + } else if ((yourSubDomRole === SubDomRole.AlwaysSubmissive) || (yourSubDomRole === SubDomRole.UsuallySubmissive)) { + if (theirSubDomRole === SubDomRole.Switch) + return new Score(Scoring.WEAK_MATCH, `Likes switches`); + + if ((theirSubDomRole === SubDomRole.AlwaysDominant) || (theirSubDomRole === SubDomRole.UsuallyDominant)) + return new Score(Scoring.MATCH, `Loves dominants`); + + if (yourRoleReversalPreference === KinkPreference.Favorite) + return new Score(Scoring.MATCH, `Loves role reversal`); + + if (yourRoleReversalPreference === KinkPreference.Yes) + return new Score(Scoring.MATCH, `Likes role reversal`); + + if ((yourSubDomRole === SubDomRole.AlwaysSubmissive) && (theirSubDomRole === SubDomRole.AlwaysSubmissive)) + return new Score(Scoring.MISMATCH, 'No submissives'); + + return new Score(Scoring.WEAK_MISMATCH, 'Hesitant on submissives'); + } + + // You must be a switch + if (theirSubDomRole === SubDomRole.Switch) + return new Score(Scoring.MATCH, `Loves switches`); + + // if (yourRoleReversalPreference === KinkPreference.Favorite) + // return new Score(Scoring.MATCH, `Loves role reversal`); + // + // if (yourRoleReversalPreference === KinkPreference.Yes) + // return new Score(Scoring.MATCH, `Likes role reversal`); + + if ((theirSubDomRole === SubDomRole.AlwaysDominant) || (theirSubDomRole === SubDomRole.UsuallyDominant)) + return new Score(Scoring.MATCH, `Loves dominants`); + + if ((theirSubDomRole === SubDomRole.AlwaysSubmissive) || (theirSubDomRole === SubDomRole.UsuallySubmissive)) + return new Score(Scoring.MATCH, `Loves submissives`); + + return new Score(Scoring.NEUTRAL); + } + + static getTagValue(tagId: number, c: Character): CharacterInfotag | undefined { return c.infotags[tagId]; } diff --git a/readme.md b/readme.md index bc4b453..afcee9d 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ This repository contains a heavily customized version of the mainline F-Chat 3.0 * Kinks are auto-compared when viewing character profile * Custom kink explanations can be expanded inline * Custom kinks are highlighted - * Gender, anthro/human preference, age, and sexual preference are highlighted if compatible or incompatible + * Gender, anthro/human preference, age, sexual preference, and sub/dom preference are highlighted if compatible or incompatible * Guestbook, friend, and group counts are visible on tabs * Character images are expanded inline * Cleaner presentation for the side bar details (age, etc.), sorted in most relevant order @@ -43,7 +43,7 @@ This repository contains a heavily customized version of the mainline F-Chat 3.0 * Search filters can be reset * General * Character profiles, guestbooks, friend lists, and image lists are cached for faster access. - * Open conversation dialog for typing in a character name + * Conversation dialog can be opened by typing in a character name ## How to Set Up Ads @@ -76,6 +76,8 @@ This repository contains a heavily customized version of the mainline F-Chat 3.0 * Configuration dialog for the player to change match weighs, preview settings, etc. * Conversation bot API * Character comparison should consider D/s preference +* Fix collapsible details on character view +* Filter unmatching ads is not channel specific -- it's either on everywhere or nowhere # F-List Exported diff --git a/site/character_page/infotag.vue b/site/character_page/infotag.vue index 640c10c..a4f5a1e 100644 --- a/site/character_page/infotag.vue +++ b/site/character_page/infotag.vue @@ -51,7 +51,7 @@ theirInterestIsRelevant(id: number): boolean { - return ((id === TagId.FurryPreference) || (id === TagId.Orientation)); + return ((id === TagId.FurryPreference) || (id === TagId.Orientation) || (id === TagId.SubDomRole)); }