From ba360bd0f830ad91d48789ef38033e45845d28f6 Mon Sep 17 00:00:00 2001 From: Marchandise Rudy Date: Sun, 4 Apr 2021 20:54:33 +0200 Subject: [PATCH] fix(policies): interval & policies (#15) * fix(policies): interval & policies * fix(logs): policies logs --- README.md | 25 ++++-- src/Configuration.ts | 13 --- src/LarbinBot.ts | 2 +- .../Commands/Tools/SchedulersToolsCommand.ts | 6 +- src/lib/Commands/index.ts | 27 ++++-- src/services/LoggerService.ts | 6 +- src/services/TwitchService.ts | 35 ++++++-- src/services/YamlService.ts | 21 +++-- tests/lib/Commands/BaseCommand.spec.ts | 87 ++++++++++++++++++- .../lib/Commands/RandomMessageCommand.spec.ts | 4 +- .../Commands/RoundRobinMessageCommand.spec.ts | 4 +- .../Tools/SchedulersToolsCommand.spec.ts | 20 ++++- 12 files changed, 196 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index d3a7c1d..8f1a34d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Completely customizable Twitch Bot. - [x] Execute commands - [x] Simple answer - [ ] Custom command -- [ ] Role-Based Access Control +- [x] Role-Based Access Control - [ ] Interaction with custom APIs - [ ] Interaction with twitch APIs - [ ] Change Title @@ -68,19 +68,22 @@ tools: # !schedulers off - type: schedulers name: '!schedulers' - policies: - onlyMods: true # IMPORTANT + policies: + mod: true + admin: true argOn: 'on' argOff: 'off' argStatus: 'status' commands: - name: '!facebook' # Command to write - random: true # Takes a random message from the list rather than following the order of the list + random: false # Takes a random message from the list rather than following the order of the list policies: - onlyMods: true # Only moderators can run this command + others: true # All messages: - 'My Facebook is https://facebook.com/example' - name: '!twitter' + policies: + others: true # All messages: - 'My Twitter is https://twitter.com/example' schedulers: @@ -118,6 +121,18 @@ events: - 'I know someone from sub, but, I say anything, alright {{ Username }} ?' ``` +### Policies + +``` yaml +# DEFAULT POLICIES VALUES +policies: + mod: false + admin: false + vip: false + sub: false + others: false +``` + ## Docker Compose ```yaml diff --git a/src/Configuration.ts b/src/Configuration.ts index f78dbf0..6826c0e 100644 --- a/src/Configuration.ts +++ b/src/Configuration.ts @@ -18,27 +18,18 @@ export class TwitchConfiguration { public Channel: string; } -/** - * Schedulers Configuration - */ -export class SchedulersConfiguration { - public Enabled: boolean; -} - /** * Provide Configuration */ export interface IConfiguration { App: AppConfiguration; Twitch: TwitchConfiguration; - Schedulers: SchedulersConfiguration; } @singleton() export class Configuration implements IConfiguration { public App: AppConfiguration; public Twitch: TwitchConfiguration; - public Schedulers: SchedulersConfiguration; constructor() { // Application @@ -54,9 +45,5 @@ export class Configuration implements IConfiguration { Password: process.env.LARBIN_TWITCH_PASSWORD as string || '', Channel: process.env.LARBIN_TWITCH_CHANNEL as string || '' }; - - this.Schedulers = { - Enabled: true - }; } } diff --git a/src/LarbinBot.ts b/src/LarbinBot.ts index a7220e3..b572903 100644 --- a/src/LarbinBot.ts +++ b/src/LarbinBot.ts @@ -38,7 +38,7 @@ export class LarbinBot implements ILarbinBot { const commands = this._yamlService.getCommands(); commands.forEach((command: ICommand) => { this._twitchService.AddCommand(command); - this._loggerService.Debug(`Command ${command.Trigger} Added.`); + this._loggerService.Debug(`Command ${command.Trigger} Added.`, command.Policies); }); // Schedulers diff --git a/src/lib/Commands/Tools/SchedulersToolsCommand.ts b/src/lib/Commands/Tools/SchedulersToolsCommand.ts index 5e1a97f..e01cfcd 100644 --- a/src/lib/Commands/Tools/SchedulersToolsCommand.ts +++ b/src/lib/Commands/Tools/SchedulersToolsCommand.ts @@ -46,17 +46,17 @@ export class SchedulersToolsCommand extends BaseCommand { } protected _statusAction(twitchService: ITwitchService): void { - const status = this._configuration.Schedulers.Enabled; + const status = twitchService.StatusSchedulers(); twitchService.Write(`The schedulers is ${status ? 'ON' : 'OFF'}`); } protected _onAction(twitchService: ITwitchService): void { - this._configuration.Schedulers.Enabled = true; + twitchService.StartSchedulers(); this._statusAction(twitchService); } protected _offAction(twitchService: ITwitchService): void { - this._configuration.Schedulers.Enabled = false; + twitchService.StopSchedulers(); this._statusAction(twitchService); } } diff --git a/src/lib/Commands/index.ts b/src/lib/Commands/index.ts index 25c2b10..d22f89b 100644 --- a/src/lib/Commands/index.ts +++ b/src/lib/Commands/index.ts @@ -1,11 +1,16 @@ import { ChatUserstate, Userstate } from 'tmi.js'; +import { Configuration } from '../../Configuration'; import { ITwitchService } from '../../services/TwitchService'; /** * Policies Command */ export class CommandPolicies { - OnlyMods = false; + public Admin = false; + public Mod = false; + public Vip = false; + public Sub = false; + public Others = false; } /** @@ -15,7 +20,7 @@ export interface ICommand { Trigger: string; Policies: CommandPolicies; Action(twitchService: ITwitchService, fullMessage: string, state: ChatUserstate): void; - CanAction(userState: Userstate): boolean; + CanAction(userState: Userstate, configuration: Configuration): boolean; } export abstract class BaseCommand implements ICommand { @@ -35,10 +40,22 @@ export abstract class BaseCommand implements ICommand { abstract Action(twitchService: ITwitchService, fullMessage: string, userState: ChatUserstate): void; public CanAction(userState: Userstate): boolean { - if (this._policies.OnlyMods && !userState.mod) { - return false; + if (this._policies.Sub && userState.subscriber) { + return true; } - return true; + if (this._policies.Vip && userState.badges?.vip) { + return true; + } + if (this._policies.Mod && userState.mod) { + return true; + } + if (this._policies.Admin && (userState.badges?.admin != null || userState.badges?.broadcaster != null)) { + return true; + } + if (this.Policies.Others) { + return this.Policies.Others; + } + return false; } } diff --git a/src/services/LoggerService.ts b/src/services/LoggerService.ts index 94d9821..41891e6 100644 --- a/src/services/LoggerService.ts +++ b/src/services/LoggerService.ts @@ -10,7 +10,7 @@ import { IConfiguration } from '../Configuration'; export interface ILoggerService { Ascii(write: string): void; Information(write: string): void; - Debug(write: string): void; + Debug(write: string, ...args: any): void; Warning(write: string): void; Error(write: string): void; } @@ -34,9 +34,9 @@ export class LoggerService implements ILoggerService { console.info(`[INFO] ${write}`); } - public Debug(write: string): void { + public Debug(write: string, ...args: any): void { if (this._configuration.App.Debug){ - console.debug(`[DEBUG] ${write}`); + console.debug(`[DEBUG] ${write}`, args); } } diff --git a/src/services/TwitchService.ts b/src/services/TwitchService.ts index cece513..e2601a4 100644 --- a/src/services/TwitchService.ts +++ b/src/services/TwitchService.ts @@ -7,6 +7,7 @@ import { IScheduler } from '../lib/Schedulers'; import { EventTypeParamsMapper } from '../mappers/EventTypeParamsMapper'; import { ILoggerService } from '.'; import { ITmiFactory } from '../factory/TmiFactory'; +import { setInterval, clearInterval } from 'timers'; /** * Provides all twitch tools @@ -15,6 +16,9 @@ export interface ITwitchService { Write(message: string): void; AddCommand(command: ICommand): ITwitchService; AddScheduler(scheduler: IScheduler): ITwitchService; + StartSchedulers(): void; + StopSchedulers(): void; + StatusSchedulers(): boolean; AddEvent(event: IEvent): ITwitchService; Listen(): void; } @@ -22,6 +26,9 @@ export interface ITwitchService { @singleton() export class TwitchService implements ITwitchService { private _commands: Array = new Array(); + private _schedulers: Array = new Array(); + private _schedulersIntervals: Array = new Array(); + private _loggerService: ILoggerService; private _configuration: IConfiguration; private _client: Client; @@ -37,10 +44,11 @@ export class TwitchService implements ITwitchService { } public Listen(): void { + this.StartSchedulers(); this._client.on('message', (channels, userstate, message) => { const command = this._commands.find((x) => message.startsWith(x.Trigger)); if (command != undefined) { - if (command.CanAction(userstate)) { + if (command.CanAction(userstate, this._configuration)) { command.Action(this, message, userstate); } } @@ -57,14 +65,29 @@ export class TwitchService implements ITwitchService { } public AddScheduler(scheduler: IScheduler): ITwitchService { - setInterval((s: IScheduler) => { - if (this._configuration.Schedulers.Enabled) { - s.Action(this); - } - }, scheduler.Minutes * 60000, scheduler); + this._schedulers.push(scheduler); return this; } + public StartSchedulers(): void { + this.StopSchedulers(); + this._schedulers.forEach((scheduler) => { + const interval = setInterval((twitchService) => scheduler.Action(twitchService), scheduler.Minutes * 60000, this); + this._schedulersIntervals.push(interval); + }) + } + + public StopSchedulers(): void { + this._schedulersIntervals.forEach((intervalId: NodeJS.Timeout) => { + clearInterval(intervalId); + }); + this._schedulersIntervals = new Array(); + } + + public StatusSchedulers(): boolean { + return this._schedulersIntervals.length > 0; + } + public AddEvent(event: IEvent): ITwitchService { this._client.on(event.Type, (...args: any[]) => { event.Action(this, EventTypeParamsMapper(event.Type, args)); diff --git a/src/services/YamlService.ts b/src/services/YamlService.ts index 9551042..09e9cc0 100644 --- a/src/services/YamlService.ts +++ b/src/services/YamlService.ts @@ -54,11 +54,18 @@ export class YamlService implements IYamlService { } protected _getCommandPolicies(policies: any): CommandPolicies { - const commandPolicies = new CommandPolicies(); - if (policies && policies.onlyMods) { - commandPolicies.OnlyMods = policies.onlyMods === 'true'; + const defaultPolicies = new CommandPolicies(); + if (!policies) { + return defaultPolicies; } - return commandPolicies; + + return { + Admin: policies.admin ?? defaultPolicies.Admin, + Mod: policies.mod ?? defaultPolicies.Mod, + Vip: policies.vip ?? defaultPolicies.Vip, + Sub: policies.sub ?? defaultPolicies.Sub, + Others: policies.others ?? defaultPolicies.Others + } as CommandPolicies; } public getCommands(): Array { const yamlContent = this.getYamlContent(); @@ -69,11 +76,11 @@ export class YamlService implements IYamlService { } yamlContent.commands?.forEach((element: any) => { - if (element.name && element.message) { + if (element.name && element.messages) { if (element.random) { - commands.push(new RandomMessageCommand(element.name, this._getCommandPolicies(element.policies), element.message)); + commands.push(new RandomMessageCommand(element.name, this._getCommandPolicies(element.policies), element.messages)); } else { - commands.push(new RoundRobinMessageCommand(element.name, this._getCommandPolicies(element.policies), element.message)); + commands.push(new RoundRobinMessageCommand(element.name, this._getCommandPolicies(element.policies), element.messages)); } } }); diff --git a/tests/lib/Commands/BaseCommand.spec.ts b/tests/lib/Commands/BaseCommand.spec.ts index 0d6f31d..c105c8b 100644 --- a/tests/lib/Commands/BaseCommand.spec.ts +++ b/tests/lib/Commands/BaseCommand.spec.ts @@ -32,7 +32,7 @@ describe('Commands - BaseCommand', function () { // Arrange const Trigger = '!trigger'; const Policies = new CommandPolicies(); - Policies.OnlyMods = isOnlyMod; + Policies.Mod = isUserMod; const command = new SampleCommand(Trigger, Policies); // Act @@ -45,9 +45,92 @@ describe('Commands - BaseCommand', function () { }; _testMods(true, true, true); - _testMods(false, true, true); + _testMods(true, false, false); + }); + + it('CanAction - Admin', async function () { + const _testMods = (isOnlyAdmin: boolean, isUserAdmin: boolean, expectResult: boolean) => { + // Arrange + const Trigger = '!trigger'; + const Policies = new CommandPolicies(); + Policies.Admin = isUserAdmin; + const command = new SampleCommand(Trigger, Policies); + + // Act + const result = command.CanAction(isUserAdmin ? { + badges: { + broadcaster: '1' + } + }: {}); + + // Assert + expect(result).toBe(expectResult); + }; + + _testMods(true, true, true); + _testMods(true, false, false); + }); + + it('CanAction - VIP', async function () { + const _testMods = (isOnlyVip: boolean, isUserVip: boolean, expectResult: boolean) => { + // Arrange + const Trigger = '!trigger'; + const Policies = new CommandPolicies(); + Policies.Vip = isUserVip; + const command = new SampleCommand(Trigger, Policies); + + // Act + const result = command.CanAction(isUserVip ? { + badges: { + vip: '1' + } + }: {}); + + // Assert + expect(result).toBe(expectResult); + }; _testMods(true, true, true); _testMods(true, false, false); }); + + it('CanAction - Sub', async function () { + const _testMods = (isOnlySub: boolean, isUserSub: boolean, expectResult: boolean) => { + // Arrange + const Trigger = '!trigger'; + const Policies = new CommandPolicies(); + Policies.Sub = isUserSub; + const command = new SampleCommand(Trigger, Policies); + + // Act + const result = command.CanAction(isUserSub ? { + subscriber: true + }: {}); + + // Assert + expect(result).toBe(expectResult); + }; + + _testMods(true, true, true); + _testMods(true, false, false); + }); + + it('CanAction - Others', async function () { + const _testMods = (others: boolean, expectResult: boolean) => { + // Arrange + const Trigger = '!trigger'; + const Policies = new CommandPolicies(); + Policies.Others = others; + const command = new SampleCommand(Trigger, Policies); + + // Act + const result = command.CanAction({}); + + // Assert + expect(result).toBe(expectResult); + }; + + _testMods(true, true); + _testMods(false, false); + }); }); diff --git a/tests/lib/Commands/RandomMessageCommand.spec.ts b/tests/lib/Commands/RandomMessageCommand.spec.ts index 1f46759..d3dabeb 100644 --- a/tests/lib/Commands/RandomMessageCommand.spec.ts +++ b/tests/lib/Commands/RandomMessageCommand.spec.ts @@ -9,9 +9,7 @@ describe('Commands - RandomMessageCommand', function () { // Arrange const Trigger = '!command'; const FullText = Trigger; - const Policies = { - OnlyMods: true - } as CommandPolicies; + const Policies = new CommandPolicies(); const messages = [ 'test1', diff --git a/tests/lib/Commands/RoundRobinMessageCommand.spec.ts b/tests/lib/Commands/RoundRobinMessageCommand.spec.ts index a6cd3e8..838baf2 100644 --- a/tests/lib/Commands/RoundRobinMessageCommand.spec.ts +++ b/tests/lib/Commands/RoundRobinMessageCommand.spec.ts @@ -9,9 +9,7 @@ describe('Commands - RoundRobinMessageCommand', function () { // Arrange const Trigger = '!command'; const FullText = Trigger; - const Policies = { - OnlyMods: true - } as CommandPolicies; + const Policies = new CommandPolicies(); const messages = [ 'test1', diff --git a/tests/lib/Commands/Tools/SchedulersToolsCommand.spec.ts b/tests/lib/Commands/Tools/SchedulersToolsCommand.spec.ts index 83e40d0..9c1dfa6 100644 --- a/tests/lib/Commands/Tools/SchedulersToolsCommand.spec.ts +++ b/tests/lib/Commands/Tools/SchedulersToolsCommand.spec.ts @@ -11,16 +11,30 @@ describe('Commands - Tools - SchedulersToolsCommand', function () { // Arrange const Trigger = '!command'; const FullText = Trigger; - const Policies = { - OnlyMods: true - } as CommandPolicies; + const Policies = new CommandPolicies(); const argStatus = 'status'; const argOn = 'on'; const argOff = 'off'; + let schedulerStatus = true; const twitchService = new Mock(); twitchService.setup(x => x.Write(It.IsAny())).returns(); + twitchService.setup(x => x.StartSchedulers).callback((interaction: any) => { + return () => { + schedulerStatus = true + } + }); + twitchService.setup(x => x.StopSchedulers).callback((interaction: any) => { + return () => { + schedulerStatus = false + } + }); + twitchService.setup(x => x.StatusSchedulers).callback((interaction: any) => { + return () => { + return schedulerStatus; + } + }); const configuration = new Configuration(); const command = new SchedulersToolsCommand(Trigger, Policies, configuration, argOn, argOff, argStatus);