minor
This commit is contained in:
		
							parent
							
								
									e5243efef4
								
							
						
					
					
						commit
						3bd1a84350
					
				
							
								
								
									
										25
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -1,15 +1,34 @@
 | 
			
		||||
# 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
 | 
			
		||||
* Shift-clicking in eicon selector adds the icon without closing the selector
 | 
			
		||||
* Minor updates to browser switching
 | 
			
		||||
 | 
			
		||||
## 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 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
 | 
			
		||||
* 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
 | 
			
		||||
* Hotfix to address connectivity issues
 | 
			
		||||
 | 
			
		||||
@ -12,3 +12,8 @@ This project contains contributions from:
 | 
			
		||||
* [Abeehiltz](https://github.com/Abeehiltz)
 | 
			
		||||
* [greyhoof](https://github.com/greyhoof)
 | 
			
		||||
* [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`
 | 
			
		||||
  * 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
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
  @click.middle.prevent.stop="toggleStickyness()"
 | 
			
		||||
  @click.right.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>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@ -16,6 +16,7 @@ import {Component, Hook, Prop} from '@f-list/vue-ts';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { EventBus } from '../chat/preview/event-bus';
 | 
			
		||||
import * as Utils from '../site/utils';
 | 
			
		||||
import { characterImage } from '../chat/common';
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
export default class IconView extends Vue {
 | 
			
		||||
@ -45,6 +46,10 @@ export default class IconView extends Vue {
 | 
			
		||||
      return `flist-character://${this.character}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAvatarUrl(): string {
 | 
			
		||||
      return characterImage(this.character);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    dismiss(force: boolean = false): void {
 | 
			
		||||
        // if (!this.preview) {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
    <div style="height:100%; display: flex; position: relative;" id="chatView" @click="userMenuHandle" @contextmenu="userMenuHandle" @touchstart.passive="userMenuHandle"
 | 
			
		||||
        @touchend="userMenuHandle">
 | 
			
		||||
        <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 href="#" @click.prevent="logOut()" class="btn"><i class="fas fa-sign-out-alt"></i>{{l('chat.logout')}}</a><br/>
 | 
			
		||||
            <div>
 | 
			
		||||
@ -543,6 +543,7 @@ import { Component, Hook, Watch } from '@f-list/vue-ts';
 | 
			
		||||
            }
 | 
			
		||||
            img {
 | 
			
		||||
                height: 40px;
 | 
			
		||||
                width: 40px;
 | 
			
		||||
                margin: -1px 5px -1px -1px;
 | 
			
		||||
            }
 | 
			
		||||
            &:first-child img {
 | 
			
		||||
 | 
			
		||||
@ -95,7 +95,8 @@ export function getStatusClasses(
 | 
			
		||||
      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) &&
 | 
			
		||||
        ((character.isFriend) || (character.isBookmarked));
 | 
			
		||||
@ -208,6 +209,11 @@ export default class UserView extends Vue {
 | 
			
		||||
      this.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Watch('character.overrides.avatarUrl')
 | 
			
		||||
    onAvatarUrlUpdate(): void {
 | 
			
		||||
      this.update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(): void {
 | 
			
		||||
      // console.log('user.view.update', this.character.name);
 | 
			
		||||
 | 
			
		||||
@ -219,7 +225,7 @@ export default class UserView extends Vue {
 | 
			
		||||
      this.matchClass = res.matchClass;
 | 
			
		||||
      this.matchScore = res.matchScore;
 | 
			
		||||
      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 {Keys} from '../keys';
 | 
			
		||||
import {Character, Conversation, Settings as ISettings} from './interfaces';
 | 
			
		||||
import core from './core';
 | 
			
		||||
 | 
			
		||||
export function profileLink(this: any | never, character: string): string {
 | 
			
		||||
    return `https://www.f-list.net/c/${character}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import { AdCenter } from './ads/ad-center';
 | 
			
		||||
import { GeneralSettings } from '../electron/common';
 | 
			
		||||
import { SiteSession } from '../site/site-session';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { initYiffbot4000Integration } from '../learn/yiffbot';
 | 
			
		||||
 | 
			
		||||
function createBBCodeParser(): BBCodeParser {
 | 
			
		||||
    const parser = new BBCodeParser();
 | 
			
		||||
@ -115,6 +116,8 @@ export function init(
 | 
			
		||||
        if(data.settingsStore !== undefined) await data.settingsStore.set('hiddenUsers', newValue);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    initYiffbot4000Integration();
 | 
			
		||||
 | 
			
		||||
    connection.onEvent('connecting', async() => {
 | 
			
		||||
        await data.reloadSettings();
 | 
			
		||||
        data.bbCodeParser = createBBCodeParser();
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  <div class="character-preview">
 | 
			
		||||
    <div v-if="match && character" class="row">
 | 
			
		||||
      <div class="col-2">
 | 
			
		||||
        <img :src="avatarUrl(character.character.name)" class="character-avatar">
 | 
			
		||||
        <img :src="getAvatarUrl()" class="character-avatar">
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="col-10">
 | 
			
		||||
@ -140,7 +140,7 @@ export default class CharacterPreview extends Vue {
 | 
			
		||||
  subDomRole?: string;
 | 
			
		||||
 | 
			
		||||
  formatTime = formatTime;
 | 
			
		||||
  readonly avatarUrl = Utils.avatarURL;
 | 
			
		||||
  // readonly avatarUrl = Utils.avatarURL;
 | 
			
		||||
 | 
			
		||||
  TagId = TagId;
 | 
			
		||||
  Score = Score;
 | 
			
		||||
@ -150,6 +150,13 @@ export default class CharacterPreview extends Vue {
 | 
			
		||||
 | 
			
		||||
  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')
 | 
			
		||||
  mounted(): void {
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
                <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"
 | 
			
		||||
                        :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>
 | 
			
		||||
                        <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>
 | 
			
		||||
@ -108,6 +108,7 @@
 | 
			
		||||
        view: Electron.BrowserView
 | 
			
		||||
        hasNew: boolean
 | 
			
		||||
        tray: Electron.Tray
 | 
			
		||||
        avatarUrl?: string;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // console.log(require('./build/tray.png').default);
 | 
			
		||||
@ -192,6 +193,16 @@
 | 
			
		||||
                menu.unshift({label: tab.user, enabled: false}, {type: 'separator'});
 | 
			
		||||
                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) => {
 | 
			
		||||
                const tab = this.tabMap[id];
 | 
			
		||||
                if(tab.hasNew) {
 | 
			
		||||
@ -270,6 +281,14 @@
 | 
			
		||||
            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 {
 | 
			
		||||
            browserWindow.setBrowserView(null!); //tslint:disable-line:no-null-keyword
 | 
			
		||||
            this.tabs.forEach(destroyTab);
 | 
			
		||||
@ -481,6 +500,7 @@
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
                height: 28px;
 | 
			
		||||
                width: 28px;
 | 
			
		||||
                margin: -5px 3px -5px -5px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -135,7 +135,7 @@ export function fixLogs(character: string): void {
 | 
			
		||||
                fs.readSync(fd, buffer, 0, 50100, pos);
 | 
			
		||||
                const deserialized = deserializeMessage(buffer, 0, (name) => ({
 | 
			
		||||
                    gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
 | 
			
		||||
                    isIgnored: false, name
 | 
			
		||||
                    isIgnored: false, name, overrides: {}
 | 
			
		||||
                }));
 | 
			
		||||
                const time = deserialized.message.time;
 | 
			
		||||
                const day = Math.floor(time.getTime() / dayMs - time.getTimezoneOffset() / 1440);
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
	<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>
 | 
			
		||||
	<link href="fa.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <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>
 | 
			
		||||
    <link href="fa.css" rel="stylesheet">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
@ -12,11 +12,18 @@ class Character implements Interfaces.Character {
 | 
			
		||||
    isBookmarked = false;
 | 
			
		||||
    isChatOp = false;
 | 
			
		||||
    isIgnored = false;
 | 
			
		||||
    overrides: CharacterOverrides = {};
 | 
			
		||||
 | 
			
		||||
    constructor(public name: string) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CharacterOverrides {
 | 
			
		||||
    avatarUrl?: string;
 | 
			
		||||
    gender?: Interfaces.Gender;
 | 
			
		||||
    status?: Interfaces.Status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class State implements Interfaces.State {
 | 
			
		||||
    characters: {[key: string]: Character | undefined} = {};
 | 
			
		||||
 | 
			
		||||
@ -56,6 +63,14 @@ class State implements Interfaces.State {
 | 
			
		||||
        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> {
 | 
			
		||||
        await methods.fieldsGet();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { Character as CharacterProfile } from '../site/character_page/interfaces';
 | 
			
		||||
import { CharacterOverrides } from './characters';
 | 
			
		||||
 | 
			
		||||
//tslint:disable:no-shadowed-variable
 | 
			
		||||
export namespace Connection {
 | 
			
		||||
@ -170,7 +171,8 @@ export namespace Character {
 | 
			
		||||
 | 
			
		||||
        readonly ownProfile: CharacterProfile;
 | 
			
		||||
 | 
			
		||||
        get(name: string): Character
 | 
			
		||||
        get(name: string): Character;
 | 
			
		||||
        setOverride(name: string, type: keyof CharacterOverrides, value: any): void;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export interface Character {
 | 
			
		||||
@ -182,6 +184,7 @@ export namespace Character {
 | 
			
		||||
        readonly isBookmarked: boolean
 | 
			
		||||
        readonly isChatOp: boolean
 | 
			
		||||
        readonly isIgnored: boolean
 | 
			
		||||
        readonly overrides: CharacterOverrides
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import { PermanentIndexedStore } from './store/types';
 | 
			
		||||
import { CharacterImage, SimpleCharacter } from '../interfaces';
 | 
			
		||||
import { Scoring } from './matcher-types';
 | 
			
		||||
import { matchesSmartFilters } from './filter/smart-filter';
 | 
			
		||||
import * as remote from '@electron/remote';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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> {
 | 
			
		||||
        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}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.updateOverrides(c);
 | 
			
		||||
 | 
			
		||||
        // 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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								learn/yiffbot.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								learn/yiffbot.ts
									
									
									
									
									
										Normal file
									
								
							@ -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">
 | 
			
		||||
            <h3>
 | 
			
		||||
                <img :src="avatarUrl(characterMatch.you.you.name)" class="thumbnail"/>
 | 
			
		||||
                <img :src="getAvatarUrl(characterMatch.you.you.name)" class="thumbnail"/>
 | 
			
		||||
                {{characterMatch.you.you.name}}
 | 
			
		||||
                <small v-if="characterMatch.youMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.you)}}</small>
 | 
			
		||||
            </h3>
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
 | 
			
		||||
        <div class="scores them">
 | 
			
		||||
            <h3>
 | 
			
		||||
                <img :src="avatarUrl(characterMatch.them.you.name)" class="thumbnail" />
 | 
			
		||||
                <img :src="getAvatarUrl(characterMatch.them.you.name)" class="thumbnail" />
 | 
			
		||||
                {{characterMatch.them.you.name}}
 | 
			
		||||
                <small v-if="characterMatch.themMultiSpecies" class="species">as {{getSpeciesStr(characterMatch.them)}}</small>
 | 
			
		||||
            </h3>
 | 
			
		||||
@ -54,8 +54,6 @@
 | 
			
		||||
        // @Prop({required: true})
 | 
			
		||||
        // readonly minimized = false;
 | 
			
		||||
 | 
			
		||||
        readonly avatarUrl = Utils.avatarURL;
 | 
			
		||||
 | 
			
		||||
        isMinimized = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -70,6 +68,16 @@
 | 
			
		||||
        //     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 {
 | 
			
		||||
            const classes: CssClassMap = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div id="character-page-sidebar" class="card bg-light">
 | 
			
		||||
        <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>
 | 
			
		||||
            <character-action-menu :character="character" @rename="showRename()" @delete="showDelete()"
 | 
			
		||||
@ -106,6 +106,7 @@
 | 
			
		||||
    import { MatchReport } from '../../learn/matcher';
 | 
			
		||||
    import MemoDialog from './memo_dialog.vue';
 | 
			
		||||
    import ReportDialog from './report_dialog.vue';
 | 
			
		||||
    import core from '../../chat/core';
 | 
			
		||||
 | 
			
		||||
    interface ShowableVueDialog extends Vue {
 | 
			
		||||
        show(): void
 | 
			
		||||
@ -152,6 +153,16 @@
 | 
			
		||||
        readonly quickInfoIds: ReadonlyArray<number> = [1, 3, 2, 49, 9, 29, 15, 41, 25]; // Do not sort these.
 | 
			
		||||
        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 {
 | 
			
		||||
            return `character-badge-${badgeName.replace('.', '-')}`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user