Skip to content

Commit

Permalink
feat(counter): command stats variables (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ealenn authored May 29, 2021
1 parent 134168f commit 80795b0
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 22 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ commands:
others: true # All
messages:
- 'My Facebook is https://facebook.com/example'
- 'Like my Facebook page https://facebook.com/example'
- name: '!twitter'
policies:
others: true # All
messages:
- 'My Twitter is https://twitter.com/example'
- 'My Twitter is https://twitter.com/example (This question has been asked {{ Count }} times)'
schedulers:
- id: 'social' # Required, is only used to make this scheduler unique
minutes: 10 # Send message every minutes
Expand Down
7 changes: 5 additions & 2 deletions src/lib/Commands/RandomMessageCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ChatUserstate } from 'tmi.js';
import { BaseCommand, CommandPolicies } from '.';
import { ICommandStats } from '../../services/Model/CommandStats';
import { ITwitchService } from '../../services/TwitchService';

/**
Expand All @@ -20,7 +22,8 @@ export class RandomMessageCommand extends BaseCommand {
return this._messages[Math.floor(Math.random() * this._messages.length)];
}

public Action(twitchService: ITwitchService): void {
twitchService.Write(this.getMessage());
public Action(twitchService: ITwitchService, fullMessage: string, userState: ChatUserstate, stats: ICommandStats): void {
const messageWithStats = this._replaceWithVariables(this.getMessage(), stats);
twitchService.Write(messageWithStats);
}
}
7 changes: 5 additions & 2 deletions src/lib/Commands/RoundRobinMessageCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ChatUserstate } from 'tmi.js';
import { BaseCommand, CommandPolicies } from '.';
import { ICommandStats } from '../../services/Model/CommandStats';
import { ITwitchService } from '../../services/TwitchService';

/**
Expand Down Expand Up @@ -26,7 +28,8 @@ export class RoundRobinMessageCommand extends BaseCommand {
return message;
}

public Action(twitchService: ITwitchService): void {
twitchService.Write(this.getMessage());
public Action(twitchService: ITwitchService, fullMessage: string, userState: ChatUserstate, stats: ICommandStats): void {
const messageWithStats = this._replaceWithVariables(this.getMessage(), stats);
twitchService.Write(messageWithStats);
}
}
11 changes: 9 additions & 2 deletions src/lib/Commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ChatUserstate, Userstate } from 'tmi.js';
import { Configuration } from '../../Configuration';
import { ICommandStats } from '../../services/Model/CommandStats';
import { ITwitchService } from '../../services/TwitchService';
import Handlebars from 'handlebars';

/**
* Policies Command
Expand All @@ -19,7 +21,7 @@ export class CommandPolicies {
export interface ICommand {
Trigger: string;
Policies: CommandPolicies;
Action(twitchService: ITwitchService, fullMessage: string, state: ChatUserstate): void;
Action(twitchService: ITwitchService, fullMessage: string, state: ChatUserstate, stats: ICommandStats): void;
CanAction(userState: Userstate, configuration: Configuration): boolean;
}

Expand All @@ -38,7 +40,7 @@ export abstract class BaseCommand implements ICommand {
this._policies = policies;
}

abstract Action(twitchService: ITwitchService, fullMessage: string, userState: ChatUserstate): void;
abstract Action(twitchService: ITwitchService, fullMessage: string, userState: ChatUserstate, stats: ICommandStats): void;
public CanAction(userState: Userstate): boolean {
if (this._policies.Sub && userState.subscriber) {
return true;
Expand All @@ -57,6 +59,11 @@ export abstract class BaseCommand implements ICommand {
}
return false;
}

protected _replaceWithVariables(message: string, params: any): string {
const Template = Handlebars.compile(message);
return Template(params);
}
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/services/Model/CommandStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Command Stats Model
*/
export interface ICommandStats {
Count: number;
LastTrigger: Date;
}

export function GenerateDefaultCommandStats(): ICommandStats {
return {
Count: 0,
LastTrigger: new Date()
};
}
16 changes: 12 additions & 4 deletions src/services/TwitchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EventTypeParamsMapper } from '../mappers/EventTypeParamsMapper';
import { ILoggerService } from '.';
import { ITmiFactory } from '../factory/TmiFactory';
import { setInterval, clearInterval } from 'timers';
import { GenerateDefaultCommandStats, ICommandStats } from './Model/CommandStats';

/**
* Provides all twitch tools
Expand All @@ -25,8 +26,8 @@ export interface ITwitchService {

@singleton()
export class TwitchService implements ITwitchService {
private _thresholdKeyDate: Map<string, Date> = new Map<string, Date>();
private _commands: Array<ICommand> = new Array<ICommand>();
private _commandsStats: Map<string, ICommandStats> = new Map<string, ICommandStats>();
private _schedulers: Array<IScheduler> = new Array<IScheduler>();
private _schedulersIntervals: Array<NodeJS.Timeout> = new Array<NodeJS.Timeout>();

Expand Down Expand Up @@ -54,14 +55,15 @@ export class TwitchService implements ITwitchService {
if (command != undefined) {
if (command.CanAction(userstate, this._configuration)) {
if (this._thresholdValidation(command.Trigger)) {
command.Action(this, message, userstate);
command.Action(this, message, userstate, this._commandsStats.get(command.Trigger) as ICommandStats);
}
}
}
}

private _thresholdValidation(key: string): boolean {
const lastTrigger = this._thresholdKeyDate.get(key) ?? new Date(1970, 1);
let commandStats = this._commandsStats.get(key);
const lastTrigger = commandStats?.LastTrigger ?? new Date(1970, 1);
const epochNow = new Date().getTime();
const epochLastTrigger = lastTrigger.getTime();
const thresholdInMiliseconds = this._configuration.App.ThresholdInSeconds * 1000;
Expand All @@ -70,7 +72,13 @@ export class TwitchService implements ITwitchService {
return false;
}

this._thresholdKeyDate.set(key, new Date());
if (!commandStats) {
commandStats = GenerateDefaultCommandStats();
}

commandStats.LastTrigger = new Date();
commandStats.Count = commandStats.Count + 1;
this._commandsStats.set(key, commandStats);
return true;
}

Expand Down
25 changes: 23 additions & 2 deletions tests/lib/Commands/RandomMessageCommand.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { CommandPolicies, RandomMessageCommand } from '../../../src/lib/Commands
import { ITwitchService } from '../../../src/services';

describe('Commands - RandomMessageCommand', function () {

it('Message', async function () {
// Arrange
const Trigger = '!command';
Expand All @@ -22,7 +21,7 @@ describe('Commands - RandomMessageCommand', function () {

// Act
for (let index = 0; index < 20; index++) {
command.Action(twitchService.object());
command.Action(twitchService.object(), FullText, {}, { Count: 42, LastTrigger: new Date() });
}

// Assert
Expand All @@ -32,4 +31,26 @@ describe('Commands - RandomMessageCommand', function () {
twitchService.verify(x => x.Write(messages[1]), Times.AtLeastOnce());
twitchService.verify(x => x.Write(messages[2]), Times.AtLeastOnce());
});

it('Stats', async function () {
// Arrange
const Trigger = '!command';
const FullText = Trigger;
const Policies = new CommandPolicies();

const messages = ['example {{ Count }} stats'];
const twitchService = new Mock<ITwitchService>();
twitchService
.setup(x => x.Write(It.IsAny()))
.callback((interaction) => {
expect(interaction.args[0]).toBe('example 42 stats');
});
const command = new RandomMessageCommand(Trigger, Policies, messages);

// Act
command.Action(twitchService.object(), FullText, {}, { Count: 42, LastTrigger: new Date() });

// Assert
twitchService.verify(x => x.Write(It.IsAny<string>()), Times.Once());
});
});
25 changes: 23 additions & 2 deletions tests/lib/Commands/RoundRobinMessageCommand.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { CommandPolicies, RoundRobinMessageCommand } from '../../../src/lib/Comm
import { ITwitchService } from '../../../src/services';

describe('Commands - RoundRobinMessageCommand', function () {

it('Message', async function () {
// Arrange
const Trigger = '!command';
Expand All @@ -22,7 +21,7 @@ describe('Commands - RoundRobinMessageCommand', function () {

// Act
for (let index = 0; index < 30; index++) {
command.Action(twitchService.object());
command.Action(twitchService.object(), FullText, {}, { Count: 42, LastTrigger: new Date() });
}

// Assert
Expand All @@ -32,4 +31,26 @@ describe('Commands - RoundRobinMessageCommand', function () {
twitchService.verify(x => x.Write(messages[1]), Times.Exactly(10));
twitchService.verify(x => x.Write(messages[2]), Times.Exactly(10));
});

it('Stats', async function () {
// Arrange
const Trigger = '!command';
const FullText = Trigger;
const Policies = new CommandPolicies();

const messages = ['example {{ Count }} stats'];
const twitchService = new Mock<ITwitchService>();
twitchService
.setup(x => x.Write(It.IsAny()))
.callback((interaction) => {
expect(interaction.args[0]).toBe('example 42 stats');
});
const command = new RoundRobinMessageCommand(Trigger, Policies, messages);

// Act
command.Action(twitchService.object(), FullText, {}, { Count: 42, LastTrigger: new Date() });

// Assert
twitchService.verify(x => x.Write(It.IsAny<string>()), Times.Once());
});
});
33 changes: 26 additions & 7 deletions tests/services/TwitchService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,42 @@ describe('Service - Twitch', function () {

it('Threshold', async function () {
// Arrange
const sleep = () => new Promise<void>(resolve => setTimeout(resolve, 2000));
const fakeCommand = '!TestThreshold';
const sleep = () => new Promise<void>(resolve => setTimeout(resolve, 2200));

// Act
const result1 = twitchService["_thresholdValidation"](mockCommand.Trigger);
const result2 = twitchService["_thresholdValidation"](mockCommand.Trigger);
const result3 = twitchService["_thresholdValidation"](mockCommand.Trigger);
const result1 = twitchService["_thresholdValidation"](fakeCommand);
const result2 = twitchService["_thresholdValidation"](fakeCommand);
const result3 = twitchService["_thresholdValidation"](fakeCommand);
await sleep();
const result4 = twitchService["_thresholdValidation"](mockCommand.Trigger);
const result5 = twitchService["_thresholdValidation"](mockCommand.Trigger);
const result4 = twitchService["_thresholdValidation"](fakeCommand);
const result5 = twitchService["_thresholdValidation"](fakeCommand);

// Assert
expect(result1).toBeTruthy();
expect(result2).toBeFalsy();
expect(result3).toBeFalsy();
expect(result4).toBeTruthy();
expect(result5).toBeFalsy();
}, 5 * 1000);
}, 10 * 1000);

it('CommandStats', async function () {
// Arrange
const fakeCommand = '!TestCommandStats';
const sleep = () => new Promise<void>(resolve => setTimeout(resolve, 2200));

// Act
twitchService["_thresholdValidation"](fakeCommand);
await sleep();
twitchService["_thresholdValidation"](fakeCommand);
await sleep();
twitchService["_thresholdValidation"](fakeCommand);
const result = twitchService["_commandsStats"].get(fakeCommand);

// Assert
expect(result).toBeDefined();
expect(result?.Count).toBe(3);
}, 10 * 1000);

it('Write', async function () {
// Arrange
Expand Down
4 changes: 4 additions & 0 deletions www/src/views/Configuration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# Debug mode
DEBUG=true
# Single command threshold per second
LARBIN_THRESHOLD=5
# Twitch Credentials
LARBIN_TWITCH_USERNAME: Larbin
LARBIN_TWITCH_PASSWORD: oic:password
Expand All @@ -37,6 +40,7 @@
others: true # All
messages:
- 'My Facebook is https://facebook.com/example'
- 'Like Facebook page https://twitter.com/example (This question has been asked {{ Count }} times)'
`" />
</v-container>

Expand Down

0 comments on commit 80795b0

Please sign in to comment.