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 = {}; /** * 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 => { 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[]) .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: '' }); } } module.exports = DictionPlugin;