Skip to content

Commit

Permalink
feat(keywords): add response randomizer
Browse files Browse the repository at this point in the history
  • Loading branch information
sogehige authored Mar 6, 2024
1 parent 03499e9 commit 81ef628
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 133 deletions.
25 changes: 5 additions & 20 deletions src/database/entity/keyword.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ManyToOne, OneToMany } from 'typeorm';
import { BaseEntity, Column, Entity, Index, PrimaryColumn } from 'typeorm';
import { z } from 'zod';

Expand All @@ -23,32 +22,18 @@ export class Keyword extends BotEntity {
@Column({ nullable: true, type: String })
group: string | null;

@OneToMany(() => KeywordResponses, (item) => item.keyword)
responses: KeywordResponses[];
}
@Column({ default: false })
areResponsesRandomized: boolean;

@Entity()
export class KeywordResponses extends BaseEntity {
@PrimaryColumn({ generated: 'uuid', type: 'uuid' })
@Column({ type: (process.env.TYPEORM_CONNECTION ?? 'better-sqlite3') !== 'better-sqlite3' ? 'json' : 'simple-json' })
responses: {
id: string;

@Column()
order: number;

@Column({ type: 'text' })
response: string;

@Column()
stopIfExecuted: boolean;

@Column({ nullable: true, type: String })
permission: string | null;

@Column()
filter: string;

@ManyToOne(() => Keyword, (item) => item.responses, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
keyword: Keyword;
}[] = [];
}

@Entity()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '../../../insertItemIntoTable.js';

export class changeKeywordsResponses1678892044040 implements MigrationInterface {
name = 'changeKeywordsResponses1678892044040';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * from \`keyword\``);
const items2 = await queryRunner.query(`SELECT * from \`keyword_responses\``);

await queryRunner.query(`DELETE from \`keyword_responses\` WHERE 1=1`);
await queryRunner.query(`DELETE from \`keyword\` WHERE 1=1`);

await queryRunner.query(`DROP TABLE \`keyword_responses\``);
await queryRunner.query(`DROP TABLE \`keyword\``);

await queryRunner.query(`CREATE TABLE \`keyword\` (\`id\` varchar(36) NOT NULL, \`keyword\` varchar(255) NOT NULL, \`enabled\` tinyint NOT NULL, \`group\` varchar(255) NULL, \`areResponsesRandomized\` tinyint NOT NULL DEFAULT 0, \`responses\` json NOT NULL, INDEX \`IDX_35e3ff88225eef1d85c951e229\` (\`keyword\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);

for (const item of items) {
item.responses = JSON.stringify(items2.filter((o: any) => o.keywordId === item.id));
await insertItemIntoTable('keyword', {
...item,
}, queryRunner);
}

return;
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '../../../insertItemIntoTable.js';

export class changeKeywordsResponses1678892044040 implements MigrationInterface {
name = 'changeKeywordsResponses1678892044040';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * from "keyword"`);
const items2 = await queryRunner.query(`SELECT * from "keyword_responses"`);

await queryRunner.query(`DELETE from "keyword_responses" WHERE 1=1`);
await queryRunner.query(`DELETE from "keyword" WHERE 1=1`);

await queryRunner.query(`DROP TABLE "keyword_responses"`);
await queryRunner.query(`DROP TABLE "keyword"`);

await queryRunner.query(`CREATE TABLE "keyword" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "areResponsesRandomized" boolean NOT NULL DEFAULT false, "keyword" character varying NOT NULL, "enabled" boolean NOT NULL, "group" character varying, "responses" json NOT NULL, CONSTRAINT "PK_affdb8c8fa5b442900cb3aa21dc" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_35e3ff88225eef1d85c951e229" ON "keyword" ("keyword") `);

for (const item of items) {
item.responses = JSON.stringify(items2.filter((o: any) => o.keywordId === item.id));
await insertItemIntoTable('keyword', {
...item,
}, queryRunner);
}

return;
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '../../../insertItemIntoTable.js';

export class changeKeywordsResponses1678892044040 implements MigrationInterface {
name = 'changeKeywordsResponses1678892044040';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * from "keyword"`);
const items2 = await queryRunner.query(`SELECT * from "keyword_responses"`);

await queryRunner.query(`DELETE from "keyword_responses" WHERE 1=1`);
await queryRunner.query(`DELETE from "keyword" WHERE 1=1`);

await queryRunner.query(`DROP TABLE "keyword_responses"`);
await queryRunner.query(`DROP TABLE "keyword"`);

await queryRunner.query(`CREATE TABLE "keyword" ("id" varchar PRIMARY KEY NOT NULL, "areResponsesRandomized" boolean NOT NULL DEFAULT (0), "keyword" varchar NOT NULL, "enabled" boolean NOT NULL, "group" varchar, "responses" text NOT NULL)`);
await queryRunner.query(`CREATE INDEX "IDX_35e3ff88225eef1d85c951e229" ON "keyword" ("keyword") `);

for (const item of items) {
item.responses = JSON.stringify(items2.filter((o: any) => o.keywordId === item.id));
await insertItemIntoTable('keyword', {
...item,
}, queryRunner);
}
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
93 changes: 40 additions & 53 deletions src/systems/keywords.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { randomUUID } from 'node:crypto';

import {
Keyword, KeywordGroup, KeywordResponses,
Keyword, KeywordGroup,
} from '@entity/keyword.js';
import _, { merge } from 'lodash-es';
import _, { orderBy, shuffle } from 'lodash-es';
import XRegExp from 'xregexp';

import System from './_interface.js';
Expand All @@ -11,7 +13,6 @@ import {
} from '../decorators.js';
import { Expects } from '../expects.js';

import { AppDataSource } from '~/database.js';
import { checkFilter } from '~/helpers/checkFilter.js';
import { isUUID, prepare } from '~/helpers/commons/index.js';
import {
Expand Down Expand Up @@ -40,7 +41,7 @@ class Keywords extends System {

app.get('/api/systems/keywords', adminMiddleware, async (req, res) => {
res.send({
data: await Keyword.find({ relations: ['responses'] }),
data: await Keyword.find(),
});
});
app.get('/api/systems/keywords/groups/', adminMiddleware, async (req, res) => {
Expand Down Expand Up @@ -69,7 +70,7 @@ class Keywords extends System {
});
app.get('/api/systems/keywords/:id', adminMiddleware, async (req, res) => {
res.send({
data: await Keyword.findOne({ where: { id: req.params.id }, relations: ['responses'] }),
data: await Keyword.findOne({ where: { id: req.params.id } }),
});
});
app.delete('/api/systems/keywords/groups/:name', adminMiddleware, async (req, res) => {
Expand All @@ -90,15 +91,6 @@ class Keywords extends System {
app.post('/api/systems/keywords', adminMiddleware, async (req, res) => {
try {
const itemToSave = await Keyword.create(req.body).save();

await AppDataSource.getRepository(KeywordResponses).delete({ keyword: { id: itemToSave.id } });
const responses = req.body.responses;
for (const response of responses) {
const resToSave = new KeywordResponses();
merge(resToSave, response);
resToSave.keyword = itemToSave;
await resToSave.save();
}
res.send({ data: itemToSave });
} catch (e) {
res.status(400).send({ errors: e });
Expand Down Expand Up @@ -142,8 +134,7 @@ class Keywords extends System {
.toArray();

let kDb = await Keyword.findOne({
relations: ['responses'],
where: { keyword: keywordRegex },
where: { keyword: keywordRegex },
});
if (!kDb) {
kDb = new Keyword();
Expand All @@ -158,14 +149,16 @@ class Keywords extends System {
throw Error('Permission ' + userlevel + ' not found.');
}

const newResponse = new KeywordResponses();
newResponse.keyword = kDb;
newResponse.order = kDb.responses.length;
newResponse.permission = pItem.id ?? defaultPermissions.VIEWERS;
newResponse.stopIfExecuted = stopIfExecuted;
newResponse.response = response;
newResponse.filter = '';
await newResponse.save();
kDb.responses.push({
id: randomUUID(),
order: kDb.responses.length,
permission: pItem.id ?? defaultPermissions.VIEWERS,
stopIfExecuted: stopIfExecuted,
response: response,
filter: '',
});

await kDb.save();
return [{
response: prepare('keywords.keyword-was-added', kDb), ...opts, id: kDb.id,
}];
Expand Down Expand Up @@ -204,9 +197,9 @@ class Keywords extends System {

let keywords: Required<Keyword>[] = [];
if (isUUID(keywordRegexOrUUID)) {
keywords = await Keyword.find({ where: { id: keywordRegexOrUUID }, relations: ['responses'] });
keywords = await Keyword.find({ where: { id: keywordRegexOrUUID } });
} else {
keywords = await Keyword.find({ where: { keyword: keywordRegexOrUUID }, relations: ['responses'] });
keywords = await Keyword.find({ where: { keyword: keywordRegexOrUUID } });
}

if (keywords.length === 0) {
Expand All @@ -230,7 +223,7 @@ class Keywords extends System {
if (stopIfExecuted) {
responseDb.stopIfExecuted = stopIfExecuted;
}
await responseDb.save();
await keyword.save();
return [{ response: prepare('keywords.keyword-was-edited', { keyword: keyword.keyword, response }), ...opts }];
}
} catch (e: any) {
Expand Down Expand Up @@ -258,8 +251,7 @@ class Keywords extends System {
// print responses
const keyword_with_responses
= await Keyword.findOne({
relations: ['responses'],
where: isUUID(keyword) ? { id: keyword } : { keyword },
where: isUUID(keyword) ? { id: keyword } : { keyword },
});

if (!keyword_with_responses || keyword_with_responses.responses.length === 0) {
Expand Down Expand Up @@ -309,28 +301,22 @@ class Keywords extends System {
return [{ response: prepare('keywords.keyword-is-ambiguous'), ...opts }];
} else {
const keyword = keywords[0];
if (rId) {
const responseDb = keyword.responses.find(o => o.order === (rId - 1));
if (!responseDb) {
return [{ response: prepare('keywords.response-was-not-found'), ...opts }];
}
// remove and reorder
let count = 0;
for (let i = 0; i < keyword.responses.length; i++) {
const response = _.orderBy(keyword.responses, 'order', 'asc')[i];
if (responseDb.id !== response.id) {
response.order = count;
count++;
await response.save();
} else {
await response.remove();
}
}
return [{ response: prepare('keywords.response-was-removed', keyword), ...opts }];
let response = prepare('keywords.keyword-was-removed', keyword);
if (rId >= 1) {
const responseDb = keyword.responses.filter(o => o.order !== (rId - 1));

// reorder
responseDb.forEach((item, index) => {
item.order = index;
});

await keyword.save();
response = prepare('keywords.response-was-removed', { keyword, response: rId });
} else {
await Keyword.remove(keyword);
return [{ response: prepare('keywords.keyword-was-removed', keyword), ...opts }];
await keyword.remove();
}
return [{ response, ...opts }];

}
} catch (e: any) {
error(e.stack);
Expand Down Expand Up @@ -391,7 +377,7 @@ class Keywords extends System {
return true;
}

const keywords = (await Keyword.find({ relations: ['responses'] })).filter((o) => {
const keywords = (await Keyword.find()).filter((o) => {
const regexp = `([!"#$%&'()*+,-.\\/:;<=>?\\b\\s]${o.keyword}[!"#$%&'()*+,-.\\/:;<=>?\\b\\s])|(^${o.keyword}[!"#$%&'()*+,-.\\/:;<=>?\\b\\s])|([!"#$%&'()*+,-.\\/:;<=>?\\b\\s]${o.keyword}$)|(^${o.keyword}$)`;
const isFoundInMessage = XRegExp(regexp, 'giu').test(opts.message);
const isEnabled = o.enabled;
Expand All @@ -405,7 +391,7 @@ class Keywords extends System {
let atLeastOnePermissionOk = false;
for (const k of keywords) {
debug('keywords.run', JSON.stringify({ k }));
const _responses: KeywordResponses[] = [];
const _responses: Keyword['responses'] = [];

// check group filter first
let group: Readonly<Required<KeywordGroup>> | null;
Expand All @@ -422,7 +408,8 @@ class Keywords extends System {
}
}

for (const r of _.orderBy(k.responses, 'order', 'asc')) {
const responses = k.areResponsesRandomized ? shuffle(k.responses) : orderBy(k.responses, 'order', 'asc');
for (const r of responses) {
let permission = r.permission ?? groupPermission;
// show warning if null permission
if (!permission) {
Expand All @@ -448,7 +435,7 @@ class Keywords extends System {
return atLeastOnePermissionOk;
}

async sendResponse(responses: (KeywordResponses)[], opts: { sender: CommandOptions['sender'], discord: CommandOptions['discord'], id: string }) {
async sendResponse(responses: (Keyword['responses']), opts: { sender: CommandOptions['sender'], discord: CommandOptions['discord'], id: string }) {
for (let i = 0; i < responses.length; i++) {
// check if response have new line, then split it and send it as separate messages
for (const response of responses[i].response.split('\n')) {
Expand Down
27 changes: 24 additions & 3 deletions test/tests/cooldowns/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,14 @@ describe('Cooldowns - @func3 - check()', () => {
it('Add koncha to keywords', async () => {
await AppDataSource.getRepository(Keyword).save({
keyword: 'koncha',
response: '$sender KonCha',
responses: [{
id: '1234',
order: 0,
response: '$sender KonCha',
stopIfExecuted: false,
permission: '0efd7b1c-e460-4167-8e06-8aaf2c170311',
filter: '',
}],
enabled: true,
});
});
Expand Down Expand Up @@ -716,7 +723,14 @@ describe('Cooldowns - @func3 - check()', () => {
it('test', async () => {
await AppDataSource.getRepository(Keyword).save({
keyword: 'me',
response: '(!me)',
responses: [{
id: '1234',
order: 0,
response: '(!me)',
stopIfExecuted: false,
permission: '0efd7b1c-e460-4167-8e06-8aaf2c170311',
filter: '',
}],
enabled: true,
});

Expand Down Expand Up @@ -765,7 +779,14 @@ describe('Cooldowns - @func3 - check()', () => {
it('test', async () => {
await AppDataSource.getRepository(Keyword).save({
keyword: 'me',
response: '(!me)',
responses: [{
id: '1234',
order: 0,
response: '(!me)',
stopIfExecuted: false,
permission: '0efd7b1c-e460-4167-8e06-8aaf2c170311',
filter: '',
}],
enabled: true,
});

Expand Down
Loading

0 comments on commit 81ef628

Please sign in to comment.