diff --git a/diction/plugin.json b/diction/plugin.json index 4527f30..fb3a532 100644 --- a/diction/plugin.json +++ b/diction/plugin.json @@ -2,7 +2,7 @@ "main": "plugin.js", "name": "diction", "description": "Find definitions for words", - "version": "1.1.0", + "version": "1.2.1", "tags": ["commands", "utility", "dictionary"], "dependencies": ["simplecommands"], "npmDependencies": [] diff --git a/diction/plugin.ts b/diction/plugin.ts index 141c2b4..84cc2f4 100644 --- a/diction/plugin.ts +++ b/diction/plugin.ts @@ -8,28 +8,60 @@ import { } from '@squeebot/core/lib/plugin'; import { IMessage, MessageResolver } from '@squeebot/core/lib/types'; -const poslist = [ - 'noun', - 'verb', - 'adjective', - 'adverb', - 'participle', - 'article', - 'pronoun', - 'preposition', - 'conjunction', - 'interjection', - 'determiner', -]; +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({ - lexicala: { - username: '', - password: '', - }, + wordnik: '', cooldown: 5, + limit: 5, }) class DictionPlugin extends Plugin { @EventListener('pluginUnload') @@ -41,13 +73,12 @@ class DictionPlugin extends Plugin { @DependencyLoad('simplecommands') addCommands(cmd: any): void { - const user = this.config.get('lexicala.username'); - const passwd = this.config.get('lexicala.password'); + const key = this.config.get('wordnik'); const rate = this.config.get('cooldown', 5); - const rauth = 'Basic ' + Buffer.from(`${user}:${passwd}`).toString('base64'); + const limit = this.config.get('limit', 5); - if (!user || !passwd) { - logger.warn('Lexicala define command is disabled due to no credentials.'); + if (!key) { + logger.warn('Wordnik define command is disabled due to no credentials.'); return; } @@ -55,66 +86,105 @@ class DictionPlugin extends Plugin { name: 'define', plugin: this.name, execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise => { - if (lastQuery > Date.now() - rate * 1000) { - msg.resolve('You\'re doing that too fast!'); - return true; - } - - let pos: string | null = simplified[0]; - if (pos && poslist.indexOf(pos.toLowerCase()) !== -1 && simplified.length > 1) { - pos = '&pos=' + pos; - } else { - pos = ''; - } - - const word = encodeURIComponent(simplified.slice(pos ? 1 : 0).join(' ')); + const word = simplified.join(' '); if (!word) { msg.resolve('Please specifiy a word or term!'); return true; } - lastQuery = Date.now(); - - let response; - try { - const s = `https://dictapi.lexicala.com/search?source=global&language=en${pos}&text=${word}`; - response = await httpGET(s, { Authorization: rauth }); - response = JSON.parse(response); - } catch (e) { - msg.resolve('Server did not respond.'); + if (lastQuery > Date.now() - rate * 1000 && !wordCache[word]) { + msg.resolve('You\'re doing that too fast!'); return true; } - if (!response.n_results || response.n_results === 0 || !response.results) { - msg.resolve('Nothing found.'); - 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[]).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 resolve = []; - for (const def of response.results) { - const wd = def.headword.text; - const wdp = def.headword.pos; - const results = def.senses.map((rep: any) => rep.definition).slice(0, 4); + const { partOfSpeech, text } = chosenDefinition; - resolve.push({ - word: wd, - type: wdp, - results, - }); - } + msg.resolve(`(${definitionIndex + 1}/${definitionCount}) ${word}${ + partOfSpeech ? ` - ${partOfSpeech}` : '' + } - ${text}`); - const str = resolve.slice(0, 4).map((rep: any) => { - return `${rep.word} (${rep.type}):\n` + rep.results.join('\n'); - }).join('\n'); - - msg.resolve(str); return true; }, match: /define (\w*)/, aliases: ['df'], - description: 'Find definitions for words', - usage: '[] ' + description: 'Find definitions for words. Call again to advance to next definition', + usage: '' }); } } diff --git a/squeebot.repo.json b/squeebot.repo.json index 86aa74f..64c7091 100644 --- a/squeebot.repo.json +++ b/squeebot.repo.json @@ -11,7 +11,7 @@ }, { "name": "diction", - "version": "1.1.0" + "version": "1.2.1" }, { "name": "fun",