minor
This commit is contained in:
parent
e5243efef4
commit
3bd1a84350
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -1,15 +1,34 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Canary
|
||||||
|
* High-quality portraits
|
||||||
|
* Add the following anywhere in your character's profile to enable high-quality portrait:
|
||||||
|
* `[url=https://some.domain.ext/path/to/image.png]Rising Portrait[/url]`
|
||||||
|
* Replace `https://some.domain.ext/path/to/image.png` with the URL to your portrait.
|
||||||
|
* The URL must point directly to an image resource, such as PNG, GIF, or JPG (think of it as `<img src="YOUR URL" />`).
|
||||||
|
* Yes, animations are supported! (GIF, APNG, AVIF, WebP)
|
||||||
|
* The image must be hosted on one of the following services:
|
||||||
|
* `f-list.net` (profile images and inline images are supported)
|
||||||
|
* [e621.net](https://e621.net)
|
||||||
|
* [imgur.com](https://imgur.com)
|
||||||
|
* [freeimage.host](https://freeimage.host)
|
||||||
|
* [redgifs.com](https://redgifs.com)
|
||||||
|
* High-quality portraits are only visible to other F-Chat Rising users; users on other clients will see your regular portrait.
|
||||||
|
* If your image is not a square, [you're gonna have a bad time](https://www.youtube.com/watch?v=6Ls5j5iz2eA).
|
||||||
|
* [YiffBot 4000](https://www.f-list.net/c/YiffBot%204000) integration
|
||||||
|
* Fix "select/unselect all" behavior in Post Ads (credit: [@FatCatClient](https://github.com/FatCatClient))
|
||||||
|
* Extended emoji support (credit: [@FatCatClient](https://github.com/FatCatClient))
|
||||||
|
|
||||||
## 1.25.1
|
## 1.25.1
|
||||||
* Shift-clicking in eicon selector adds the icon without closing the selector
|
* Shift-clicking in eicon selector adds the icon without closing the selector
|
||||||
* Minor updates to browser switching
|
* Minor updates to browser switching
|
||||||
|
|
||||||
## 1.25.0
|
## 1.25.0
|
||||||
* Added option for switching browsers (Credit: [@greyhoof](https://github.com/greyhoof))
|
* Added option for switching browsers (credit: [@greyhoof](https://github.com/greyhoof))
|
||||||
* Fixed broken adblocker
|
* Fixed broken adblocker
|
||||||
* Fixed incorrect BBCode rendering of `[collapse=[hr]test[hr]]` (Credit: [@Abeehiltz](https://github.com/Abeehiltz))
|
* Fixed incorrect BBCode rendering of `[collapse=[hr]test[hr]]` (credit: [@Abeehiltz](https://github.com/Abeehiltz))
|
||||||
* Fixed TikTok previews
|
* Fixed TikTok previews
|
||||||
* Switched `node-sass` to `sass` for ARM64 compatibility (Credit: [@WhiteHusky](https://github.com/WhiteHusky))
|
* Switched `node-sass` to `sass` for ARM64 compatibility (credit: [@WhiteHusky](https://github.com/WhiteHusky))
|
||||||
|
|
||||||
## 1.24.2
|
## 1.24.2
|
||||||
* Hotfix to address connectivity issues
|
* Hotfix to address connectivity issues
|
||||||
|
|
|
@ -12,3 +12,8 @@ This project contains contributions from:
|
||||||
* [Abeehiltz](https://github.com/Abeehiltz)
|
* [Abeehiltz](https://github.com/Abeehiltz)
|
||||||
* [greyhoof](https://github.com/greyhoof)
|
* [greyhoof](https://github.com/greyhoof)
|
||||||
* [F-List Team](https://github.com/f-list) (original F-Chat 3.0 client)
|
* [F-List Team](https://github.com/f-list) (original F-Chat 3.0 client)
|
||||||
|
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
Some emojis designed by [OpenMoji](https://openmoji.org/) – the open-source emoji and icon project. License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/#)
|
||||||
|
|
||||||
|
|
11
PRIVACY.md
11
PRIVACY.md
|
@ -26,6 +26,17 @@ When the 'Link Preview' feature is used, F-Chat Rising will connect to the URL b
|
||||||
* Twitter previews are proxied through `api.fxtwitter.com`
|
* Twitter previews are proxied through `api.fxtwitter.com`
|
||||||
* YouTube previews are proxied through `yewtu.be`
|
* YouTube previews are proxied through `yewtu.be`
|
||||||
|
|
||||||
|
## High-Quality Portraits
|
||||||
|
When 'High-Quality Portraits' feature is used, F-Chat Rising may connect to the following additional domains:
|
||||||
|
|
||||||
|
* iili.io
|
||||||
|
* e621.net
|
||||||
|
* imgur.com
|
||||||
|
* freeimage.host
|
||||||
|
* redgifs.com
|
||||||
|
|
||||||
|
If you are concerned about your security or privacy, consider disabling the high quality portraits feature in F-Chat Rising settings.
|
||||||
|
|
||||||
## Locally Stored Data
|
## Locally Stored Data
|
||||||
F-Chat Rising stores data on your computer. This data contains conversation logs, settings, cache, and other
|
F-Chat Rising stores data on your computer. This data contains conversation logs, settings, cache, and other
|
||||||
information such as custom dictionary words. By default, the data is stored in:
|
information such as custom dictionary words. By default, the data is stored in:
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
@click.middle.prevent.stop="toggleStickyness()"
|
@click.middle.prevent.stop="toggleStickyness()"
|
||||||
@click.right.passive="dismiss(true)"
|
@click.right.passive="dismiss(true)"
|
||||||
@click.left.passive="dismiss(true)"
|
@click.left.passive="dismiss(true)"
|
||||||
><img :src="`${Utils.staticDomain}images/avatar/${character.toLowerCase()}.png`" class="character-avatar icon" :title="character" :alt="character" v-once></a>
|
><img :src="getAvatarUrl()" class="character-avatar icon" :title="character" :alt="character" v-once></a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -16,6 +16,7 @@ import {Component, Hook, Prop} from '@f-list/vue-ts';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { EventBus } from '../chat/preview/event-bus';
|
import { EventBus } from '../chat/preview/event-bus';
|
||||||
import * as Utils from '../site/utils';
|
import * as Utils from '../site/utils';
|
||||||
|
import { characterImage } from '../chat/common';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class IconView extends Vue {
|
export default class IconView extends Vue {
|
||||||
|
@ -45,6 +46,10 @@ export default class IconView extends Vue {
|
||||||
return `flist-character://${this.character}`;
|
return `flist-character://${this.character}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvatarUrl(): string {
|
||||||
|
return characterImage(this.character);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dismiss(force: boolean = false): void {
|
dismiss(force: boolean = false): void {
|
||||||
// if (!this.preview) {
|
// if (!this.preview) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div style="height:100%; display: flex; position: relative;" id="chatView" @click="userMenuHandle" @contextmenu="userMenuHandle" @touchstart.passive="userMenuHandle"
|
<div style="height:100%; display: flex; position: relative;" id="chatView" @click="userMenuHandle" @contextmenu="userMenuHandle" @touchstart.passive="userMenuHandle"
|
||||||
@touchend="userMenuHandle">
|
@touchend="userMenuHandle">
|
||||||
<sidebar id="sidebar" :label="l('chat.menu')" icon="fa-bars">
|
<sidebar id="sidebar" :label="l('chat.menu')" icon="fa-bars">
|
||||||
<img :src="characterImage(ownCharacter.name)" v-if="showAvatars" style="float:left;margin-right:5px;margin-top:5px;width:70px"/>
|
<img :src="characterImage(ownCharacter.name)" v-if="showAvatars" style="float:left;margin-right:5px;margin-top:5px;width:70px; height: 70px;"/>
|
||||||
<a target="_blank" :href="ownCharacterLink" class="btn" style="display:block">{{ownCharacter.name}}</a>
|
<a target="_blank" :href="ownCharacterLink" class="btn" style="display:block">{{ownCharacter.name}}</a>
|
||||||
<a href="#" @click.prevent="logOut()" class="btn"><i class="fas fa-sign-out-alt"></i>{{l('chat.logout')}}</a><br/>
|
<a href="#" @click.prevent="logOut()" class="btn"><i class="fas fa-sign-out-alt"></i>{{l('chat.logout')}}</a><br/>
|
||||||
<div>
|
<div>
|
||||||
|
@ -543,6 +543,7 @@ import { Component, Hook, Watch } from '@f-list/vue-ts';
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
margin: -1px 5px -1px -1px;
|
margin: -1px 5px -1px -1px;
|
||||||
}
|
}
|
||||||
&:first-child img {
|
&:first-child img {
|
||||||
|
|
|
@ -95,7 +95,8 @@ export function getStatusClasses(
|
||||||
smartFilterIcon = 'user-filter fas fa-filter';
|
smartFilterIcon = 'user-filter fas fa-filter';
|
||||||
}
|
}
|
||||||
|
|
||||||
const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none';
|
const baseGender = character.overrides.gender || character.gender;
|
||||||
|
const gender = baseGender !== undefined ? baseGender.toLowerCase() : 'none';
|
||||||
|
|
||||||
const isBookmark = (showBookmark) && (core.connection.isOpen) && (core.state.settings.colorBookmarks) &&
|
const isBookmark = (showBookmark) && (core.connection.isOpen) && (core.state.settings.colorBookmarks) &&
|
||||||
((character.isFriend) || (character.isBookmarked));
|
((character.isFriend) || (character.isBookmarked));
|
||||||
|
@ -208,6 +209,11 @@ export default class UserView extends Vue {
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch('character.overrides.avatarUrl')
|
||||||
|
onAvatarUrlUpdate(): void {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
update(): void {
|
update(): void {
|
||||||
// console.log('user.view.update', this.character.name);
|
// console.log('user.view.update', this.character.name);
|
||||||
|
|
||||||
|
@ -219,7 +225,7 @@ export default class UserView extends Vue {
|
||||||
this.matchClass = res.matchClass;
|
this.matchClass = res.matchClass;
|
||||||
this.matchScore = res.matchScore;
|
this.matchScore = res.matchScore;
|
||||||
this.userClass = res.userClass;
|
this.userClass = res.userClass;
|
||||||
this.avatarUrl = characterImage(this.character.name);
|
this.avatarUrl = this.character.overrides.avatarUrl || characterImage(this.character.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
import {isToday} from 'date-fns';
|
import {isToday} from 'date-fns';
|
||||||
import {Keys} from '../keys';
|
import {Keys} from '../keys';
|
||||||
import {Character, Conversation, Settings as ISettings} from './interfaces';
|
import {Character, Conversation, Settings as ISettings} from './interfaces';
|
||||||
|
import core from './core';
|
||||||
|
|
||||||
export function profileLink(this: any | never, character: string): string {
|
export function profileLink(this: any | never, character: string): string {
|
||||||
return `https://www.f-list.net/c/${character}`;
|
return `https://www.f-list.net/c/${character}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function characterImage(this: any | never, character: string): string {
|
export function characterImage(this: any | never, character: string): string {
|
||||||
|
const c = core.characters.get(character);
|
||||||
|
|
||||||
|
if (c.overrides.avatarUrl) {
|
||||||
|
return c.overrides.avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
return `https://static.f-list.net/images/avatar/${character.toLowerCase()}.png`;
|
return `https://static.f-list.net/images/avatar/${character.toLowerCase()}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AdCenter } from './ads/ad-center';
|
||||||
import { GeneralSettings } from '../electron/common';
|
import { GeneralSettings } from '../electron/common';
|
||||||
import { SiteSession } from '../site/site-session';
|
import { SiteSession } from '../site/site-session';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { initYiffbot4000Integration } from '../learn/yiffbot';
|
||||||
|
|
||||||
function createBBCodeParser(): BBCodeParser {
|
function createBBCodeParser(): BBCodeParser {
|
||||||
const parser = new BBCodeParser();
|
const parser = new BBCodeParser();
|
||||||
|
@ -115,6 +116,8 @@ export function init(
|
||||||
if(data.settingsStore !== undefined) await data.settingsStore.set('hiddenUsers', newValue);
|
if(data.settingsStore !== undefined) await data.settingsStore.set('hiddenUsers', newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initYiffbot4000Integration();
|
||||||
|
|
||||||
connection.onEvent('connecting', async() => {
|
connection.onEvent('connecting', async() => {
|
||||||
await data.reloadSettings();
|
await data.reloadSettings();
|
||||||
data.bbCodeParser = createBBCodeParser();
|
data.bbCodeParser = createBBCodeParser();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="character-preview">
|
<div class="character-preview">
|
||||||
<div v-if="match && character" class="row">
|
<div v-if="match && character" class="row">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<img :src="avatarUrl(character.character.name)" class="character-avatar">
|
<img :src="getAvatarUrl()" class="character-avatar">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
|
@ -140,7 +140,7 @@ export default class CharacterPreview extends Vue {
|
||||||
subDomRole?: string;
|
subDomRole?: string;
|
||||||
|
|
||||||
formatTime = formatTime;
|
formatTime = formatTime;
|
||||||
readonly avatarUrl = Utils.avatarURL;
|
// readonly avatarUrl = Utils.avatarURL;
|
||||||
|
|
||||||
TagId = TagId;
|
TagId = TagId;
|
||||||
Score = Score;
|
Score = Score;
|
||||||
|
@ -150,6 +150,13 @@ export default class CharacterPreview extends Vue {
|
||||||
|
|
||||||
conversation?: Conversation.Message[];
|
conversation?: Conversation.Message[];
|
||||||
|
|
||||||
|
getAvatarUrl(): string {
|
||||||
|
if (this.onlineCharacter && this.onlineCharacter.overrides.avatarUrl) {
|
||||||
|
return this.onlineCharacter.overrides.avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.avatarURL(this.characterName || this.character?.character.name || '');
|
||||||
|
}
|
||||||
|
|
||||||
@Hook('mounted')
|
@Hook('mounted')
|
||||||
mounted(): void {
|
mounted(): void {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<li v-for="(tab,index) in tabs" :key="'tab-' + index" class="nav-item" @click.middle="remove(tab)">
|
<li v-for="(tab,index) in tabs" :key="'tab-' + index" class="nav-item" @click.middle="remove(tab)">
|
||||||
<a href="#" @click.prevent="show(tab)" class="nav-link tab"
|
<a href="#" @click.prevent="show(tab)" class="nav-link tab"
|
||||||
:class="{active: tab === activeTab, hasNew: tab.hasNew && tab !== activeTab}">
|
:class="{active: tab === activeTab, hasNew: tab.hasNew && tab !== activeTab}">
|
||||||
<img v-if="tab.user" :src="'https://static.f-list.net/images/avatar/' + tab.user.toLowerCase() + '.png'"/>
|
<img v-if="tab.user || tab.avatarUrl" :src="getAvatarImage(tab)"/>
|
||||||
<span class="d-sm-inline d-none">{{tab.user || l('window.newTab')}}</span>
|
<span class="d-sm-inline d-none">{{tab.user || l('window.newTab')}}</span>
|
||||||
<a href="#" :aria-label="l('action.close')" style="margin-left:10px;padding:0;color:inherit;text-decoration:none"
|
<a href="#" :aria-label="l('action.close')" style="margin-left:10px;padding:0;color:inherit;text-decoration:none"
|
||||||
@click.stop="remove(tab)"><i class="fa fa-times"></i>
|
@click.stop="remove(tab)"><i class="fa fa-times"></i>
|
||||||
|
@ -108,6 +108,7 @@
|
||||||
view: Electron.BrowserView
|
view: Electron.BrowserView
|
||||||
hasNew: boolean
|
hasNew: boolean
|
||||||
tray: Electron.Tray
|
tray: Electron.Tray
|
||||||
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(require('./build/tray.png').default);
|
// console.log(require('./build/tray.png').default);
|
||||||
|
@ -192,6 +193,16 @@
|
||||||
menu.unshift({label: tab.user, enabled: false}, {type: 'separator'});
|
menu.unshift({label: tab.user, enabled: false}, {type: 'separator'});
|
||||||
tab.tray.setContextMenu(remote.Menu.buildFromTemplate(menu));
|
tab.tray.setContextMenu(remote.Menu.buildFromTemplate(menu));
|
||||||
});
|
});
|
||||||
|
electron.ipcRenderer.on('update-avatar-url', (_e: Event, characterName: string, url: string) => {
|
||||||
|
const tab = this.tabs.find((tab) => tab.user === characterName);
|
||||||
|
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.set(tab, 'avatarUrl', url);
|
||||||
|
// tab.avatarUrl = url;
|
||||||
|
});
|
||||||
electron.ipcRenderer.on('disconnect', (_e: Event, id: number) => {
|
electron.ipcRenderer.on('disconnect', (_e: Event, id: number) => {
|
||||||
const tab = this.tabMap[id];
|
const tab = this.tabMap[id];
|
||||||
if(tab.hasNew) {
|
if(tab.hasNew) {
|
||||||
|
@ -270,6 +281,14 @@
|
||||||
log.debug('init.window.mounted');
|
log.debug('init.window.mounted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvatarImage(tab: Tab) {
|
||||||
|
if (tab.avatarUrl) {
|
||||||
|
return tab.avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'https://static.f-list.net/images/avatar/' + (tab.user || '').toLowerCase() + '.png';
|
||||||
|
}
|
||||||
|
|
||||||
destroyAllTabs(): void {
|
destroyAllTabs(): void {
|
||||||
browserWindow.setBrowserView(null!); //tslint:disable-line:no-null-keyword
|
browserWindow.setBrowserView(null!); //tslint:disable-line:no-null-keyword
|
||||||
this.tabs.forEach(destroyTab);
|
this.tabs.forEach(destroyTab);
|
||||||
|
@ -481,6 +500,7 @@
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
margin: -5px 3px -5px -5px;
|
margin: -5px 3px -5px -5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ export function fixLogs(character: string): void {
|
||||||
fs.readSync(fd, buffer, 0, 50100, pos);
|
fs.readSync(fd, buffer, 0, 50100, pos);
|
||||||
const deserialized = deserializeMessage(buffer, 0, (name) => ({
|
const deserialized = deserializeMessage(buffer, 0, (name) => ({
|
||||||
gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
|
gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
|
||||||
isIgnored: false, name
|
isIgnored: false, name, overrides: {}
|
||||||
}));
|
}));
|
||||||
const time = deserialized.message.time;
|
const time = deserialized.message.time;
|
||||||
const day = Math.floor(time.getTime() / dayMs - time.getTimezoneOffset() / 1440);
|
const day = Math.floor(time.getTime() / dayMs - time.getTimezoneOffset() / 1440);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src file: data: https://static.f-list.net http://static.f-list.net; connect-src *">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src file: data: https://static.f-list.net http://static.f-list.net https://iili.io https://static1.e621.net https://i.imgur.com https://freeimage.host https://v3.redgifs.com; connect-src *">
|
||||||
<title>F-Chat</title>
|
<title>F-Chat</title>
|
||||||
<link href="fa.css" rel="stylesheet">
|
<link href="fa.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src https://static.f-list.net">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src https://static.f-list.net https://iili.io https://static1.e621.net https://i.imgur.com https://freeimage.host https://v3.redgifs.com">
|
||||||
<title>F-Chat</title>
|
<title>F-Chat</title>
|
||||||
<link href="fa.css" rel="stylesheet">
|
<link href="fa.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -12,11 +12,18 @@ class Character implements Interfaces.Character {
|
||||||
isBookmarked = false;
|
isBookmarked = false;
|
||||||
isChatOp = false;
|
isChatOp = false;
|
||||||
isIgnored = false;
|
isIgnored = false;
|
||||||
|
overrides: CharacterOverrides = {};
|
||||||
|
|
||||||
constructor(public name: string) {
|
constructor(public name: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CharacterOverrides {
|
||||||
|
avatarUrl?: string;
|
||||||
|
gender?: Interfaces.Gender;
|
||||||
|
status?: Interfaces.Status;
|
||||||
|
}
|
||||||
|
|
||||||
class State implements Interfaces.State {
|
class State implements Interfaces.State {
|
||||||
characters: {[key: string]: Character | undefined} = {};
|
characters: {[key: string]: Character | undefined} = {};
|
||||||
|
|
||||||
|
@ -56,6 +63,14 @@ class State implements Interfaces.State {
|
||||||
character.statusText = decodeHTML(text);
|
character.statusText = decodeHTML(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOverride(name: string, type: 'avatarUrl', value: string | undefined): void;
|
||||||
|
setOverride(name: string, type: 'gender', value: Interfaces.Gender | undefined): void;
|
||||||
|
setOverride(name: string, type: 'status', value: Interfaces.Status | undefined): void;
|
||||||
|
setOverride(name: string, type: keyof CharacterOverrides, value: any): void {
|
||||||
|
const char = this.get(name);
|
||||||
|
char.overrides[type] = value;
|
||||||
|
}
|
||||||
|
|
||||||
async resolveOwnProfile(): Promise<void> {
|
async resolveOwnProfile(): Promise<void> {
|
||||||
await methods.fieldsGet();
|
await methods.fieldsGet();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Character as CharacterProfile } from '../site/character_page/interfaces';
|
import { Character as CharacterProfile } from '../site/character_page/interfaces';
|
||||||
|
import { CharacterOverrides } from './characters';
|
||||||
|
|
||||||
//tslint:disable:no-shadowed-variable
|
//tslint:disable:no-shadowed-variable
|
||||||
export namespace Connection {
|
export namespace Connection {
|
||||||
|
@ -170,7 +171,8 @@ export namespace Character {
|
||||||
|
|
||||||
readonly ownProfile: CharacterProfile;
|
readonly ownProfile: CharacterProfile;
|
||||||
|
|
||||||
get(name: string): Character
|
get(name: string): Character;
|
||||||
|
setOverride(name: string, type: keyof CharacterOverrides, value: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Character {
|
export interface Character {
|
||||||
|
@ -182,6 +184,7 @@ export namespace Character {
|
||||||
readonly isBookmarked: boolean
|
readonly isBookmarked: boolean
|
||||||
readonly isChatOp: boolean
|
readonly isChatOp: boolean
|
||||||
readonly isIgnored: boolean
|
readonly isIgnored: boolean
|
||||||
|
readonly overrides: CharacterOverrides
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { PermanentIndexedStore } from './store/types';
|
||||||
import { CharacterImage, SimpleCharacter } from '../interfaces';
|
import { CharacterImage, SimpleCharacter } from '../interfaces';
|
||||||
import { Scoring } from './matcher-types';
|
import { Scoring } from './matcher-types';
|
||||||
import { matchesSmartFilters } from './filter/smart-filter';
|
import { matchesSmartFilters } from './filter/smart-filter';
|
||||||
|
import * as remote from '@electron/remote';
|
||||||
|
|
||||||
|
|
||||||
export interface MetaRecord {
|
export interface MetaRecord {
|
||||||
|
@ -148,6 +149,54 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSafeImageURL(url: string): boolean {
|
||||||
|
if (url.match(/^https?:\/\/static\.f-list\.net\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?imgur\.com\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?freeimage\.host\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?iili\.io\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?redgifs\.com\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.match(/^https?:\/\/([a-z0-9\-.]+\.)?e621\.net\//i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOverrides(c: ComplexCharacter): void {
|
||||||
|
const match = c.character.description.match(/\[url=(.*?)]\s*?Rising\s*?Portrait\s*?\[\/url]/i);
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
const avatarUrl = match[1].trim();
|
||||||
|
|
||||||
|
if (!this.isSafeImageURL(avatarUrl)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.character.name === core.characters.ownCharacter.name) {
|
||||||
|
const parent = remote.getCurrentWindow().webContents;
|
||||||
|
|
||||||
|
parent.send('update-avatar-url', c.character.name, avatarUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
core.characters.setOverride(c.character.name, 'avatarUrl', avatarUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async register(c: ComplexCharacter, skipStore: boolean = false): Promise<CharacterCacheRecord> {
|
async register(c: ComplexCharacter, skipStore: boolean = false): Promise<CharacterCacheRecord> {
|
||||||
const k = AsyncCache.nameKey(c.character.name);
|
const k = AsyncCache.nameKey(c.character.name);
|
||||||
|
@ -158,6 +207,8 @@ export class ProfileCache extends AsyncCache<CharacterCacheRecord> {
|
||||||
console.log(`Storing score 0 for character ${c.character.name}`);
|
console.log(`Storing score 0 for character ${c.character.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateOverrides(c);
|
||||||
|
|
||||||
// const totalScoreDimensions = match ? Matcher.countScoresTotal(match) : 0;
|
// const totalScoreDimensions = match ? Matcher.countScoresTotal(match) : 0;
|
||||||
// const dimensionsAtScoreLevel = match ? (Matcher.countScoresAtLevel(match, score) || 0) : 0;
|
// const dimensionsAtScoreLevel = match ? (Matcher.countScoresAtLevel(match, score) || 0) : 0;
|
||||||
// const dimensionsAboveScoreLevel = match ? (Matcher.countScoresAboveLevel(match, Math.max(score, Scoring.WEAK_MATCH))) : 0;
|
// const dimensionsAboveScoreLevel = match ? (Matcher.countScoresAboveLevel(match, Math.max(score, Scoring.WEAK_MATCH))) : 0;
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { EventBus } from '../chat/preview/event-bus';
|
||||||
|
import { Message } from '../chat/common';
|
||||||
|
import core from '../chat/core';
|
||||||
|
|
||||||
|
export function initYiffbot4000Integration() {
|
||||||
|
EventBus.$on('private-message', ({ message }: { message: Message }) => {
|
||||||
|
if (message.sender.name === 'YiffBot 4000') {
|
||||||
|
const match = message.text.match(/\[spoiler](.*?FChatRisingBotManifest.*?)\[\/spoiler]/i);
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
try {
|
||||||
|
const manifest = JSON.parse(match[1]);
|
||||||
|
|
||||||
|
if (manifest.type === 'FChatRisingBotManifest' && manifest.version >= 1) {
|
||||||
|
const char = core.characters.get('YiffBot 4000');
|
||||||
|
|
||||||
|
char.overrides.avatarUrl = manifest.avatarUrl;
|
||||||
|
char.overrides.gender = manifest.gender;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('FChatRisingBotManifest.error', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<div class="scores you">
|
<div class="scores you">
|
||||||
<h3>
|
<h3>
|
||||||
<img :src="avatarUrl(characterMatch.you.you.name)" class="thumbnail"/>
|
<img :src="getAvatarUrl(characterMatch.you.you.name)" class="thumbnail"/>
|
||||||
{{characterMatch.you.you.name}}
|
{{characterMatch.you.you.name}}
|
||||||
<small v-if="characterMatch.youMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.you)}}</small>
|
<small v-if="characterMatch.youMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.you)}}</small>
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
<div class="scores them">
|
<div class="scores them">
|
||||||
<h3>
|
<h3>
|
||||||
<img :src="avatarUrl(characterMatch.them.you.name)" class="thumbnail" />
|
<img :src="getAvatarUrl(characterMatch.them.you.name)" class="thumbnail" />
|
||||||
{{characterMatch.them.you.name}}
|
{{characterMatch.them.you.name}}
|
||||||
<small v-if="characterMatch.themMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.them)}}</small>
|
<small v-if="characterMatch.themMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.them)}}</small>
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -54,8 +54,6 @@
|
||||||
// @Prop({required: true})
|
// @Prop({required: true})
|
||||||
// readonly minimized = false;
|
// readonly minimized = false;
|
||||||
|
|
||||||
readonly avatarUrl = Utils.avatarURL;
|
|
||||||
|
|
||||||
isMinimized = false;
|
isMinimized = false;
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,6 +68,16 @@
|
||||||
// this.isMinimized = this.minimized;
|
// this.isMinimized = this.minimized;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
getAvatarUrl(name: string) {
|
||||||
|
const c = core.characters.get(name);
|
||||||
|
|
||||||
|
if (c.overrides.avatarUrl) {
|
||||||
|
return c.overrides.avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.avatarURL(name);
|
||||||
|
}
|
||||||
|
|
||||||
getScoreClass(score: Score): CssClassMap {
|
getScoreClass(score: Score): CssClassMap {
|
||||||
const classes: CssClassMap = {};
|
const classes: CssClassMap = {};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="character-page-sidebar" class="card bg-light">
|
<div id="character-page-sidebar" class="card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<img :src="avatarUrl(character.character.name)" class="character-avatar" style="width: 100%; height: auto;">
|
<img :src="getAvatarUrl()" class="character-avatar" style="width: 100%; height: auto;">
|
||||||
|
|
||||||
<div v-if="character.character.title" class="character-title">{{ character.character.title }}</div>
|
<div v-if="character.character.title" class="character-title">{{ character.character.title }}</div>
|
||||||
<character-action-menu :character="character" @rename="showRename()" @delete="showDelete()"
|
<character-action-menu :character="character" @rename="showRename()" @delete="showDelete()"
|
||||||
|
@ -106,6 +106,7 @@
|
||||||
import { MatchReport } from '../../learn/matcher';
|
import { MatchReport } from '../../learn/matcher';
|
||||||
import MemoDialog from './memo_dialog.vue';
|
import MemoDialog from './memo_dialog.vue';
|
||||||
import ReportDialog from './report_dialog.vue';
|
import ReportDialog from './report_dialog.vue';
|
||||||
|
import core from '../../chat/core';
|
||||||
|
|
||||||
interface ShowableVueDialog extends Vue {
|
interface ShowableVueDialog extends Vue {
|
||||||
show(): void
|
show(): void
|
||||||
|
@ -152,6 +153,16 @@
|
||||||
readonly quickInfoIds: ReadonlyArray<number> = [1, 3, 2, 49, 9, 29, 15, 41, 25]; // Do not sort these.
|
readonly quickInfoIds: ReadonlyArray<number> = [1, 3, 2, 49, 9, 29, 15, 41, 25]; // Do not sort these.
|
||||||
readonly avatarUrl = Utils.avatarURL;
|
readonly avatarUrl = Utils.avatarURL;
|
||||||
|
|
||||||
|
getAvatarUrl(): string {
|
||||||
|
const onlineCharacter = core.characters.get(this.character.character.name);
|
||||||
|
|
||||||
|
if (onlineCharacter && onlineCharacter.overrides.avatarUrl) {
|
||||||
|
return onlineCharacter.overrides.avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.avatarURL(this.character.character.name);
|
||||||
|
}
|
||||||
|
|
||||||
badgeClass(badgeName: string): string {
|
badgeClass(badgeName: string): string {
|
||||||
return `character-badge-${badgeName.replace('.', '-')}`;
|
return `character-badge-${badgeName.replace('.', '-')}`;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue