Enforce 5s cooldown between ads across all connected characters
This commit is contained in:
		
							parent
							
								
									9879195a41
								
							
						
					
					
						commit
						d2de87b554
					
				@ -2,7 +2,10 @@
 | 
			
		||||
 | 
			
		||||
## Canary
 | 
			
		||||
*   Fix caching issue that causes cache misses on charater page metadata
 | 
			
		||||
*   Fix ad posting issue that sometimes disconnects characters if multiple characters are in use
 | 
			
		||||
*   URL preview fixes for Redgifs, Gelbooru, Tumblr, and Gifmixxx
 | 
			
		||||
*   All dependencies are now up to date
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 1.0.1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -126,8 +126,8 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
            core.connection.onEvent('closed', (isReconnect) => {
 | 
			
		||||
                if(process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                      'connection.closed',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'connection.closed',
 | 
			
		||||
                        character: core.characters.ownCharacter?.name,
 | 
			
		||||
                        error: this.error,
 | 
			
		||||
                        isReconnect
 | 
			
		||||
@ -141,6 +141,7 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
                this.connecting = false;
 | 
			
		||||
 | 
			
		||||
                AdManager.onConnectionClosed();
 | 
			
		||||
                core.adCoordinator.clear();
 | 
			
		||||
 | 
			
		||||
                document.title = l('title');
 | 
			
		||||
            });
 | 
			
		||||
@ -149,8 +150,8 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
 | 
			
		||||
                if(process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                      'connection.connecting',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'connection.connecting',
 | 
			
		||||
                        character: core.characters.ownCharacter?.name
 | 
			
		||||
                      }
 | 
			
		||||
                    );
 | 
			
		||||
@ -165,8 +166,8 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
            core.connection.onEvent('connected', () => {
 | 
			
		||||
                if(process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                      'connection.connected',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'connection.connected',
 | 
			
		||||
                        character: core.characters.ownCharacter?.name
 | 
			
		||||
                      }
 | 
			
		||||
                    );
 | 
			
		||||
@ -185,8 +186,8 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
            core.connection.onError((e) => {
 | 
			
		||||
                if(process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                      'connection.error',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'connection.error',
 | 
			
		||||
                        error: errorToString(e),
 | 
			
		||||
                        character: core.characters.ownCharacter?.name
 | 
			
		||||
                      }
 | 
			
		||||
@ -214,7 +215,7 @@ import {InlineDisplayMode} from '../interfaces';
 | 
			
		||||
 | 
			
		||||
            // skipping await
 | 
			
		||||
            // tslint:disable-next-line: no-floating-promises
 | 
			
		||||
            await core.notifications.initSounds(['attention', 'login', 'logout', 'modalert', 'newnote']);
 | 
			
		||||
            core.notifications.initSounds(['attention', 'login', 'logout', 'modalert', 'newnote']);
 | 
			
		||||
 | 
			
		||||
            core.connection.connect(this.selectedCharacter.name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								chat/ads/ad-coordinator-guest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								chat/ads/ad-coordinator-guest.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
 | 
			
		||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
 | 
			
		||||
import core from '../core';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class AdCoordinatorGuest {
 | 
			
		||||
    protected pendingAds: Record<string, any> = {};
 | 
			
		||||
    protected adCounter = 0;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        ipcRenderer.on('grant-send-ad', (_event: IpcRendererEvent, adId: string) => this.processPendingAd(adId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    processPendingAd(adId: string): void {
 | 
			
		||||
        if (!(adId in this.pendingAds)) {
 | 
			
		||||
            log.debug('adid.pending.miss', {adId, character: core.characters.ownCharacter?.name});
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log.debug('adid.pending.process', {adId, character: core.characters.ownCharacter?.name});
 | 
			
		||||
 | 
			
		||||
        this.pendingAds[adId].resolve();
 | 
			
		||||
 | 
			
		||||
        delete this.pendingAds[adId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    requestTurnToPostAd(): Promise<void> {
 | 
			
		||||
        return new Promise(
 | 
			
		||||
          (resolve, reject) => {
 | 
			
		||||
            const adId = `${Math.round(Math.random() * 1000000)}-${this.adCounter++}-${Date.now()}`;
 | 
			
		||||
 | 
			
		||||
            this.pendingAds[adId] = { resolve, reject, from: Date.now() };
 | 
			
		||||
 | 
			
		||||
            log.debug('adid.request', {adId, character: core.characters.ownCharacter?.name});
 | 
			
		||||
 | 
			
		||||
            ipcRenderer.send('request-send-ad', adId);
 | 
			
		||||
          }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    clear(): void {
 | 
			
		||||
      _.each(this.pendingAds, (pa) => (pa.reject()));
 | 
			
		||||
 | 
			
		||||
      console.debug('adid.clear', _.keys(this.pendingAds), core.characters.ownCharacter?.name);
 | 
			
		||||
 | 
			
		||||
      this.pendingAds = {};
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								chat/ads/ad-coordinator-host.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								chat/ads/ad-coordinator-host.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
import throat from 'throat';
 | 
			
		||||
import Bluebird from 'bluebird';
 | 
			
		||||
import { IpcMainEvent } from 'electron';
 | 
			
		||||
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
 | 
			
		||||
 | 
			
		||||
const adCoordinatorThroat = throat(1);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class AdCoordinatorHost {
 | 
			
		||||
  static readonly MIN_DISTANCE = 5000;
 | 
			
		||||
  private lastPost = Date.now();
 | 
			
		||||
 | 
			
		||||
  async processAdRequest(event: IpcMainEvent, adId: string) {
 | 
			
		||||
    await adCoordinatorThroat(
 | 
			
		||||
      async() => {
 | 
			
		||||
        const sinceLastPost = Date.now() - this.lastPost;
 | 
			
		||||
        const waitTime = Math.max(0, AdCoordinatorHost.MIN_DISTANCE - sinceLastPost);
 | 
			
		||||
 | 
			
		||||
        log.debug('adid.request.host', {adId, sinceLastPost, waitTime});
 | 
			
		||||
 | 
			
		||||
        await Bluebird.delay(waitTime);
 | 
			
		||||
 | 
			
		||||
        log.debug('adid.request.host.grant', {adId, sinceLastPost, waitTime});
 | 
			
		||||
 | 
			
		||||
        event.reply('grant-send-ad', adId);
 | 
			
		||||
 | 
			
		||||
        this.lastPost = Date.now();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -71,8 +71,8 @@ export class AdManager {
 | 
			
		||||
 | 
			
		||||
                if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                      'adManager.sendAdToChannel',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'sendAdToChannel',
 | 
			
		||||
                        character: core.characters.ownCharacter?.name,
 | 
			
		||||
                        channel: conv.channel.name,
 | 
			
		||||
                        throatDelta: throatTime - initTime,
 | 
			
		||||
 | 
			
		||||
@ -147,6 +147,7 @@ abstract class Conversation implements Interfaces.Conversation {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrivateConversation extends Conversation implements Interfaces.PrivateConversation {
 | 
			
		||||
    readonly name = this.character.name;
 | 
			
		||||
    readonly context = CommandContext.Private;
 | 
			
		||||
@ -402,7 +403,12 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
 | 
			
		||||
            async() => {
 | 
			
		||||
                const throatTime = Date.now();
 | 
			
		||||
 | 
			
		||||
                await Conversation.testPostDelay();
 | 
			
		||||
                await Promise.all(
 | 
			
		||||
                    [
 | 
			
		||||
                        await Conversation.testPostDelay(),
 | 
			
		||||
                        await core.adCoordinator.requestTurnToPostAd()
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                const delayTime = Date.now();
 | 
			
		||||
 | 
			
		||||
@ -411,8 +417,8 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
 | 
			
		||||
 | 
			
		||||
                if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
                    log.debug(
 | 
			
		||||
                    'conversation.sendAd',
 | 
			
		||||
                      {
 | 
			
		||||
                        type: 'sendAd',
 | 
			
		||||
                        character: core.characters.ownCharacter?.name,
 | 
			
		||||
                        channel: this.channel.name,
 | 
			
		||||
                        throatDelta: throatTime - initTime,
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import BBCodeParser from './bbcode';
 | 
			
		||||
import {Settings as SettingsImpl} from './common';
 | 
			
		||||
import Conversations from './conversations';
 | 
			
		||||
import {Channel, Character, Connection, Conversation, Logs, Notifications, Settings, State as StateInterface} from './interfaces';
 | 
			
		||||
import { AdCoordinatorGuest } from './ads/ad-coordinator-guest';
 | 
			
		||||
 | 
			
		||||
function createBBCodeParser(): BBCodeParser {
 | 
			
		||||
    const parser = new BBCodeParser();
 | 
			
		||||
@ -65,6 +66,8 @@ const data = {
 | 
			
		||||
    characters: <Character.State | undefined>undefined,
 | 
			
		||||
    notifications: <Notifications | undefined>undefined,
 | 
			
		||||
    cache: <CacheManager | undefined>undefined,
 | 
			
		||||
    adCoordinator: <AdCoordinatorGuest | undefined>undefined,
 | 
			
		||||
 | 
			
		||||
    register<K extends 'characters' | 'conversations' | 'channels'>(module: K, subState: VueState[K]): void {
 | 
			
		||||
        Vue.set(vue, module, subState);
 | 
			
		||||
        (<VueState[K]>data[module]) = subState;
 | 
			
		||||
@ -86,6 +89,7 @@ export function init(this: any, connection: Connection, logsClass: new() => Logs
 | 
			
		||||
    data.settingsStore = new settingsClass();
 | 
			
		||||
    data.notifications = new notificationsClass();
 | 
			
		||||
    data.cache = new CacheManager();
 | 
			
		||||
    data.adCoordinator = new AdCoordinatorGuest();
 | 
			
		||||
 | 
			
		||||
    // tslint:disable-next-line no-floating-promises
 | 
			
		||||
    data.cache.start();
 | 
			
		||||
@ -111,6 +115,7 @@ export interface Core {
 | 
			
		||||
    readonly bbCodeParser: BBCodeParser
 | 
			
		||||
    readonly notifications: Notifications
 | 
			
		||||
    readonly cache: CacheManager
 | 
			
		||||
    readonly adCoordinator: AdCoordinatorGuest;
 | 
			
		||||
 | 
			
		||||
    watch<T>(getter: (this: VueState) => T, callback: WatchHandler<T>): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,8 @@ import fetch from 'node-fetch';
 | 
			
		||||
import MenuItemConstructorOptions = Electron.MenuItemConstructorOptions;
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import DownloadItem = Electron.DownloadItem;
 | 
			
		||||
import { AdCoordinatorHost } from '../chat/ads/ad-coordinator-host';
 | 
			
		||||
import { IpcMainEvent } from 'electron';
 | 
			
		||||
 | 
			
		||||
//tslint:disable-next-line:no-require-imports
 | 
			
		||||
const pck = require('./package.json');
 | 
			
		||||
@ -542,6 +544,12 @@ function onReady(): void {
 | 
			
		||||
        const index = characters.indexOf(character);
 | 
			
		||||
        if(index !== -1) characters.splice(index, 1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const adCoordinator = new AdCoordinatorHost();
 | 
			
		||||
    electron.ipcMain.on('request-send-ad', (event: IpcMainEvent, adId: string) => (adCoordinator.processAdRequest(event, adId)));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const emptyBadge = electron.nativeImage.createEmpty();
 | 
			
		||||
 | 
			
		||||
    //tslint:disable-next-line:no-require-imports no-unsafe-any
 | 
			
		||||
 | 
			
		||||
@ -131,8 +131,8 @@ export default class Connection implements Interfaces.Connection {
 | 
			
		||||
 | 
			
		||||
        if(res.error === 'Invalid ticket.' || res.error === 'Your login ticket has expired (five minutes) or no ticket requested.') {
 | 
			
		||||
            log.debug(
 | 
			
		||||
              'api.ticket.loss',
 | 
			
		||||
              {
 | 
			
		||||
                type: 'api.ticket.loss',
 | 
			
		||||
                error: res.error,
 | 
			
		||||
                character: core.characters.ownCharacter?.name,
 | 
			
		||||
                deltaToLastApiCall: Date.now() - lastFetch,
 | 
			
		||||
@ -146,8 +146,8 @@ export default class Connection implements Interfaces.Connection {
 | 
			
		||||
 | 
			
		||||
        if(res.error !== '') {
 | 
			
		||||
            log.debug(
 | 
			
		||||
              'error.api.query',
 | 
			
		||||
              {
 | 
			
		||||
                type: 'error.api.query',
 | 
			
		||||
                error: res.error,
 | 
			
		||||
                endpoint,
 | 
			
		||||
                character: core.characters.ownCharacter?.name,
 | 
			
		||||
@ -237,8 +237,8 @@ export default class Connection implements Interfaces.Connection {
 | 
			
		||||
            console.log(`https://www.f-list.net/json/getApiTicket.php, gap: ${Date.now() - lastApiTicketFetch}ms`);
 | 
			
		||||
 | 
			
		||||
            log.debug(
 | 
			
		||||
              'api.getTicket',
 | 
			
		||||
              {
 | 
			
		||||
                type: 'api.getTicket',
 | 
			
		||||
                character: core.characters.ownCharacter?.name,
 | 
			
		||||
                deltaToLastApiCall: Date.now() - lastFetch,
 | 
			
		||||
                deltaToLastApiTicket: Date.now() - lastApiTicketFetch
 | 
			
		||||
@ -257,8 +257,8 @@ export default class Connection implements Interfaces.Connection {
 | 
			
		||||
            console.error('API Ticket Error', data.error);
 | 
			
		||||
 | 
			
		||||
            log.error(
 | 
			
		||||
              'error.api.getTicket',
 | 
			
		||||
              {
 | 
			
		||||
                type: 'error.api.getTicket',
 | 
			
		||||
                character: core.characters.ownCharacter.name,
 | 
			
		||||
                error: data.error,
 | 
			
		||||
                deltaToLastApiCall: Date.now() - lastFetch,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user