Skip to content

Commit

Permalink
feat(cooldown): add default cooldowns settings (#4154)
Browse files Browse the repository at this point in the history
  • Loading branch information
sogehige committed Sep 25, 2020
1 parent e7fe3b8 commit ffb68a8
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 15 deletions.
4 changes: 3 additions & 1 deletion locales/en/ui.systems.cooldown.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"settings": {
"enabled": "Status",
"cooldownNotifyAsWhisper": "Whisper cooldown informations",
"cooldownNotifyAsChat": "Chat message cooldown informations"
"cooldownNotifyAsChat": "Chat message cooldown informations",
"defaultCooldownOfCommandsInSeconds": "Default cooldown for commands (in seconds)",
"defaultCooldownOfKeywordsInSeconds": "Default cooldown for keywords (in seconds)"
}
}
106 changes: 92 additions & 14 deletions src/bot/systems/cooldown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XRegExp from 'xregexp';

import { isOwner, parserReply, prepare } from '../commons';
import * as constants from '../constants';
import { command, default_permission, parser, rollback, settings } from '../decorators';
import { command, default_permission, parser, permission_settings, rollback, settings } from '../decorators';
import Expects from '../expects';
import Parser from '../parser';
import { permission } from '../helpers/permissions';
Expand All @@ -16,8 +16,10 @@ import { adminEndpoint } from '../helpers/socket';
import { Keyword } from '../database/entity/keyword';
import customCommands from './customcommands';
import { debug } from '../helpers/log';
import permissions from '../permissions';

const cache: { id: string; cooldowns: CooldownInterface[] }[] = [];
const defaultCooldowns: { name: string; lastRunAt: number, permId: string }[] = [];

/*
* !cooldown [keyword|!command] [global|user] [seconds] [true/false] - set cooldown for keyword or !command - 0 for disable, true/false set quiet mode
Expand All @@ -29,6 +31,11 @@ const cache: { id: string; cooldowns: CooldownInterface[] }[] = [];
*/

class Cooldown extends System {
@permission_settings('default', [ permission.CASTERS ])
defaultCooldownOfCommandsInSeconds = 0;
@permission_settings('default', [ permission.CASTERS ])
defaultCooldownOfKeywordsInSeconds = 0;

@settings()
cooldownNotifyAsWhisper = false;

Expand Down Expand Up @@ -124,7 +131,7 @@ class Cooldown extends System {
@parser({ priority: constants.HIGH })
async check (opts: ParserOptions): Promise<boolean> {
try {
let data: CooldownInterface[];
let data: (CooldownInterface | { type: 'default'; canBeRunAt: number; isEnabled: true; name: string; permId: string })[] = [];
let viewer: CooldownViewerInterface | undefined;
let timestamp, now;
const [cmd, subcommand] = new Expects(opts.message)
Expand Down Expand Up @@ -158,17 +165,41 @@ class Cooldown extends System {
}

const cooldown = await getRepository(CooldownEntity).findOne({ where: { name }, relations: ['viewers'] });
if (!cooldown) { // command is not on cooldown -> recheck with text only
const replace = new RegExp(`${XRegExp.escape(name)}`, 'ig');
const message = opts.message.replace(replace, '').trim();
if (message.length > 0 && opts.message !== message) {
debug('cooldown.check', `Command ${name} not on cooldown, checking: ${message}`);
return this.check({...opts, message});
if (!cooldown) {
const defaultValue = await this.getPermissionBasedSettingsValue('defaultCooldownOfCommandsInSeconds');
const permId = await permissions.getUserHighestPermission(opts.sender.userId);
if (permId === null) {
// do nothing if user doesn't have permission
return false;
}

// user group have some default cooldown
if (defaultValue[permId] > 0) {
const canBeRunAt = (defaultCooldowns.find(o =>
o.permId === permId
&& o.name === cmd
)?.lastRunAt ?? 0) + defaultValue[permId] * 1000;
data.push({
isEnabled: true,
name: cmd,
type: 'default',
canBeRunAt,
permId,
});
} else {
return true;
// command is not on cooldown or default cooldown -> recheck with text only
const replace = new RegExp(`${XRegExp.escape(name)}`, 'ig');
const message = opts.message.replace(replace, '').trim();
if (message.length > 0 && opts.message !== message) {
debug('cooldown.check', `Command ${name} not on cooldown, checking: ${message}`);
return this.check({...opts, message});
} else {
return true;
}
}
} else {
data = [cooldown];
}
data = [cooldown];
} else { // text
let [keywords, cooldowns] = await Promise.all([
getRepository(Keyword).find(),
Expand All @@ -180,12 +211,36 @@ class Cooldown extends System {
});

data = [];
_.each(keywords, (keyword) => {
for (const keyword of keywords) {
const cooldown = _.find(cooldowns, (o) => o.name.toLowerCase() === keyword.keyword.toLowerCase());
if (keyword.enabled && cooldown) {
data.push(cooldown);
if (keyword.enabled) {
if (cooldown) {
data.push(cooldown);
} else {
const defaultValue = await this.getPermissionBasedSettingsValue('defaultCooldownOfKeywordsInSeconds');
const permId = await permissions.getUserHighestPermission(opts.sender.userId);
if (permId === null) {
// do nothing if user doesn't have permission
return false;
}

// user group have some default cooldown
if (defaultValue[permId] > 0) {
const canBeRunAt = (defaultCooldowns.find(o =>
o.permId === permId
&& o.name === keyword.keyword
)?.lastRunAt ?? 0) + defaultValue[permId] * 1000;
data.push({
isEnabled: true,
name: keyword.keyword,
type: 'default',
permId,
canBeRunAt,
});
}
}
}
});
}
}
if (!_.some(data, { isEnabled: true })) { // parse ok if all cooldowns are disabled
return true;
Expand All @@ -199,6 +254,29 @@ class Cooldown extends System {

const affectedCooldowns: CooldownInterface[] = [];
for (const cooldown of data) {
if (cooldown.type === 'default') {
debug('cooldown.check', `Checking default cooldown ${cooldown.name}`);
if (cooldown.canBeRunAt >= Date.now()) {
debug('cooldown.check', `${opts.sender.username}#${opts.sender.userId} have ${cooldown.name} on global default cooldown, remaining ${Math.ceil((cooldown.canBeRunAt - Date.now()) / 1000)}s`);
result = false;
} else {
const savedCooldown = defaultCooldowns.find(o =>
o.permId === cooldown.permId
&& o.name === cooldown.name
);
if (savedCooldown) {
savedCooldown.lastRunAt = Date.now();
} else {
defaultCooldowns.push({
lastRunAt: Date.now(),
name: cooldown.name,
permId: cooldown.permId,
});
}
result = true;
}
continue;
}
if ((isOwner(opts.sender) && !cooldown.isOwnerAffected) || (user.isModerator && !cooldown.isModeratorAffected) || (user.isSubscriber && !cooldown.isSubscriberAffected) || (user.isFollower && !cooldown.isFollowerAffected)) {
result = true;
continue;
Expand Down
133 changes: 133 additions & 0 deletions test/tests/cooldowns/check_default_values.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* global describe it before */


require('../../general.js');

const { getRepository } = require('typeorm');
const { Cooldown, CooldownViewer } = require('../../../dest/database/entity/cooldown');
const { User } = require('../../../dest/database/entity/user');
const { Keyword } = require('../../../dest/database/entity/keyword');

const assert = require('assert');

const db = require('../../general.js').db;
const message = require('../../general.js').message;
const time = require('../../general.js').time;

const cooldown = (require('../../../dest/systems/cooldown')).default;
const customcommands = (require('../../../dest/systems/customcommands')).default;
const gamble = (require('../../../dest/games/gamble')).default;

// users
const owner = { userId: Math.floor(Math.random() * 100000), username: 'soge__', badges: {} };
const usermod1 = { userId: Math.floor(Math.random() * 100000), username: 'usermod1', badges: { moderator: 1 } };
const subuser1 = { userId: Math.floor(Math.random() * 100000), username: 'subuser1', badges: { subscriber: 1 } };
const testUser = { userId: Math.floor(Math.random() * 100000), username: 'test', badges: {} };
const testUser2 = { userId: Math.floor(Math.random() * 100000), username: 'test2', badges: {} };


describe('Cooldowns - default check', () => {
describe('command - default', async () => {
before(async () => {
await db.cleanup();
await message.prepare();

gamble.enabled = true;
cooldown.defaultCooldownOfCommandsInSeconds = 5;

await getRepository(User).save({ username: usermod1.username, userId: usermod1.userId, isModerator: true });
await getRepository(User).save({ username: subuser1.username, userId: subuser1.userId, isSubscriber: true });
await getRepository(User).save({ username: testUser.username, userId: testUser.userId });
await getRepository(User).save({ username: testUser2.username, userId: testUser2.userId });
await getRepository(User).save({ username: owner.username, userId: owner.userId, isSubscriber: true });
});

after(() => {
gamble.enabled = false;
cooldown.defaultCooldownOfCommandsInSeconds = 0;
});

it('testuser should not be affected by cooldown', async () => {
const isOk = await cooldown.check({ sender: testUser, message: '!test 10' });
assert(isOk);
});

it('testuser should be affected by cooldown second time', async () => {
const isOk = await cooldown.check({ sender: testUser, message: '!test 15' });
assert(!isOk);
});

it('wait 2 seconds', async () => {
await time.waitMs(2000);
});

it('testuser2 should be affected by cooldown', async () => {
const isOk = await cooldown.check({ sender: testUser2, message: '!test 25' });
assert(!isOk);
});

it('subuser1 should NOT be affected by cooldown as it is different permGroup', async () => {
const isOk = await cooldown.check({ sender: subuser1, message: '!test 25' });
assert(isOk);
});

it('wait 4 seconds', async () => {
await time.waitMs(4000);
});

it('testuser2 should not be affected by cooldown second time', async () => {
const isOk = await cooldown.check({ sender: testUser2, message: '!test 15' });
assert(isOk);
});
});

describe('keyword - default', async () => {
before(async () => {
await db.cleanup();
await message.prepare();

gamble.enabled = true;
cooldown.defaultCooldownOfKeywordsInSeconds = 5;

await getRepository(User).save({ username: usermod1.username, userId: usermod1.userId, isModerator: true });
await getRepository(User).save({ username: subuser1.username, userId: subuser1.userId, isSubscriber: true });
await getRepository(User).save({ username: testUser.username, userId: testUser.userId });
await getRepository(User).save({ username: testUser2.username, userId: testUser2.userId });
await getRepository(User).save({ username: owner.username, userId: owner.userId, isSubscriber: true });
});

after(() => {
gamble.enabled = false;
cooldown.defaultCooldownOfKeywordsInSeconds = 0;
});

it('test', async () => {
await getRepository(Keyword).save({
keyword: 'me',
response: '(!me)',
enabled: true,
});

let isOk = await cooldown.check({ sender: testUser, message: 'me' });
assert(isOk);

isOk = await cooldown.check({ sender: testUser, message: 'me' });
assert(!isOk); // second should fail

isOk = await cooldown.check({ sender: subuser1, message: 'me' });
assert(isOk); // another perm group should not fail

await time.waitMs(2000);

isOk = await cooldown.check({ sender: testUser2, message: 'me' });
assert(!isOk); // another user should fail as well

await time.waitMs(4000);

isOk = await cooldown.check({ sender: testUser2, message: 'me' });
assert(isOk);

});
});
});

0 comments on commit ffb68a8

Please sign in to comment.