263 lines
6.9 KiB
TypeScript
263 lines
6.9 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,
|
|
ProtocolFeatureFlag,
|
|
} from '@squeebot/core/lib/types';
|
|
|
|
interface IApiResponse {
|
|
partOfSpeech: string;
|
|
text: string;
|
|
word: string;
|
|
}
|
|
|
|
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(): void {
|
|
const 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(' ');
|
|
const short = msg.source.supports(
|
|
ProtocolFeatureFlag.SHORT_FORM_MESSAGING
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
const skip = short ? 1 : 5;
|
|
let chosenDefinitions: 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 startIndex = alreadyAsked.index;
|
|
|
|
if (alreadyAsked.lastTime < Date.now() - 5 * 60 * 1000) {
|
|
startIndex = 0;
|
|
}
|
|
|
|
const nextIndex =
|
|
startIndex + skip >= cached.definitions.length
|
|
? 0
|
|
: startIndex + skip;
|
|
|
|
alreadyAsked.lastTime = Date.now();
|
|
alreadyAsked.index = nextIndex;
|
|
definitionIndex = startIndex;
|
|
chosenDefinitions = cached.definitions.slice(
|
|
startIndex,
|
|
startIndex + skip
|
|
);
|
|
} else {
|
|
chosenDefinitions = cached.definitions.slice(0, skip);
|
|
cached.lastIndex.push({
|
|
name: userTarget,
|
|
index: skip,
|
|
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=${
|
|
short ? limit : 100
|
|
}&api_key=${key}`;
|
|
response = await httpGET(s);
|
|
response = JSON.parse(response) as IApiResponse[];
|
|
} catch (e) {
|
|
msg.resolve('Server did not respond.');
|
|
return true;
|
|
}
|
|
|
|
if (!response?.length || response.every((item) => !item.word)) {
|
|
msg.resolve('No definitions found.');
|
|
return true;
|
|
}
|
|
|
|
const cached: IWordCache = {
|
|
lastIndex: [
|
|
{
|
|
name: userTarget,
|
|
index: skip,
|
|
lastTime: Date.now(),
|
|
},
|
|
],
|
|
definitions: response
|
|
.filter((entry) => entry.text)
|
|
.map((data) => ({
|
|
partOfSpeech: data.partOfSpeech,
|
|
text: data.text.replace(/(<([^>]+)>)/gi, ''),
|
|
})),
|
|
inserted: Date.now(),
|
|
};
|
|
|
|
wordCache[word] = cached;
|
|
chosenDefinitions = cached.definitions.slice(0, skip);
|
|
definitionCount = cached.definitions.length;
|
|
logger.log(`[diction] Dictionary cached the word "${word}"`);
|
|
}
|
|
|
|
if (short) {
|
|
const { partOfSpeech, text } = chosenDefinitions[0];
|
|
msg.resolve(
|
|
`(${definitionIndex + 1}/${definitionCount}) ${word}${
|
|
partOfSpeech ? ` - ${partOfSpeech}` : ''
|
|
} - ${text}`
|
|
);
|
|
} else {
|
|
const generatedMessages: string[] = [word];
|
|
let lastPartOfSpeech = '';
|
|
for (const definition of chosenDefinitions) {
|
|
if (
|
|
!lastPartOfSpeech ||
|
|
lastPartOfSpeech !== definition.partOfSpeech
|
|
) {
|
|
lastPartOfSpeech = definition.partOfSpeech;
|
|
generatedMessages.push(
|
|
msg.source.format.format(
|
|
'bold',
|
|
definition.partOfSpeech || 'other'
|
|
)
|
|
);
|
|
}
|
|
|
|
generatedMessages.push(` ${definition.text}`);
|
|
}
|
|
|
|
if (definitionIndex + skip < definitionCount) {
|
|
generatedMessages.push(
|
|
`(${definitionCount - definitionIndex - skip} more...)`
|
|
);
|
|
}
|
|
|
|
msg.resolve(generatedMessages.join('\n'));
|
|
}
|
|
|
|
return true;
|
|
},
|
|
match: /define (\w*)/,
|
|
aliases: ['df', 'word'],
|
|
description:
|
|
'Find definitions for words. Call again to advance to next definition',
|
|
usage: '<word>',
|
|
});
|
|
}
|
|
|
|
@DependencyLoad('cron')
|
|
public cronLoaded(cron: any): void {
|
|
cron.registerTimer(this, '0 0 * * *', () => flushCache());
|
|
}
|
|
}
|
|
|
|
module.exports = DictionPlugin;
|