plugins-evert/diction/plugin.ts

195 lines
5.2 KiB
TypeScript

import { httpGET } from '@squeebot/core/lib/common';
import { logger } from '@squeebot/core/lib/core';
import {
Plugin,
Configurable,
EventListener,
DependencyLoad
} from '@squeebot/core/lib/plugin';
import { IMessage, MessageResolver } from '@squeebot/core/lib/types';
interface IDefinition {
partOfSpeech: string;
text: string;
}
interface IWordCache {
lastIndex: {
name: string;
index: number;
lastTime: number;
}[];
definitions: IDefinition[];
inserted: number;
}
let lastQuery = 0;
const wordCache: Record<string, IWordCache> = {};
/**
* Find and remove words that haven't been looked up for a day
*/
function flushCache() {
let oldestWords: string[] = [];
Object.keys(wordCache).forEach((word) => {
const cached = wordCache[word];
let notOld = false;
for (const person of cached.lastIndex) {
if (person.lastTime > Date.now() - 24 * 60 * 60 * 1000) {
notOld = true;
break;
}
}
if (!notOld) {
oldestWords.push(word);
}
});
if (!oldestWords.length) {
return;
}
oldestWords.forEach((item) => {
delete wordCache[item];
});
logger.log(`[diction] Dictionary cleared of the previous words ${oldestWords.join(', ')}.`);
}
@Configurable({
wordnik: '',
cooldown: 5,
limit: 5,
})
class DictionPlugin extends Plugin {
@EventListener('pluginUnload')
public unloadEventHandler(plugin: string | Plugin): void {
if (plugin === this.name || plugin === this) {
this.emit('pluginUnloaded', this);
}
}
@DependencyLoad('simplecommands')
addCommands(cmd: any): void {
const key = this.config.get('wordnik');
const rate = this.config.get('cooldown', 5);
const limit = this.config.get('limit', 5);
if (!key) {
logger.warn('Wordnik define command is disabled due to no credentials.');
return;
}
cmd.registerCommand({
name: 'define',
plugin: this.name,
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
const word = simplified.join(' ');
if (!word) {
msg.resolve('Please specifiy a word or term!');
return true;
}
if (lastQuery > Date.now() - rate * 1000 && !wordCache[word]) {
msg.resolve('You\'re doing that too fast!');
return true;
}
let chosenDefinition: IDefinition;
let definitionCount = 0;
let definitionIndex = 0;
// Check cached definitions
const userTarget = msg.fullSenderID as string;
if (wordCache[word]) {
const cached = wordCache[word];
const alreadyAsked = cached.lastIndex.find((item) => item.name === userTarget);
definitionCount = cached.definitions.length;
if (alreadyAsked) {
let nextI = alreadyAsked.index + 1;
if (nextI >= cached.definitions.length) {
nextI = 0;
}
if (alreadyAsked.lastTime < Date.now() - 5 * 60 * 1000) {
nextI = 0;
}
alreadyAsked.lastTime = Date.now();
alreadyAsked.index = nextI;
definitionIndex = nextI;
chosenDefinition = cached.definitions[nextI];
} else {
chosenDefinition = cached.definitions[0];
cached.lastIndex.push({
name: userTarget,
index: 0,
lastTime: Date.now(),
});
}
} else {
// Request definition
const encodedWord = encodeURIComponent(word);
lastQuery = Date.now();
let response;
try {
const s = `https://api.wordnik.com/v4/word.json/${encodedWord}/definitions?limit=${limit}&api_key=${key}`;
response = await httpGET(s);
response = JSON.parse(response);
} catch (e) {
msg.resolve('Server did not respond.');
return true;
}
if (!response || !response.length) {
msg.resolve('No definitions found.');
return true;
}
const cached: IWordCache = {
lastIndex: [
{
name: userTarget,
index: 0,
lastTime: Date.now(),
}
],
definitions: (response as Record<string, string>[])
.filter(entry => entry.text)
.map(({ partOfSpeech, text }) => ({
partOfSpeech, text: text.replace(/(<([^>]+)>)/ig, '')
})),
inserted: Date.now(),
}
wordCache[word] = cached;
chosenDefinition = cached.definitions[0];
definitionCount = cached.definitions.length;
logger.log(`[diction] Dictionary cached the word "${word}"`);
flushCache();
}
const { partOfSpeech, text } = chosenDefinition;
msg.resolve(`(${definitionIndex + 1}/${definitionCount}) ${word}${
partOfSpeech ? ` - ${partOfSpeech}` : ''
} - ${text}`);
return true;
},
match: /define (\w*)/,
aliases: ['df', 'word'],
description: 'Find definitions for words. Call again to advance to next definition',
usage: '<word>'
});
}
}
module.exports = DictionPlugin;