Simp-O-Matic

Dumb Discord bot in TS.
git clone git://git.knutsen.co/Simp-O-Matic
Log | Files | Refs | README | LICENSE

commit 57f9aa5b1b62b868873ae2f103068d90d34c048b
parent 9988c4ad1258af32328b6421b78794d5c29fcb35
Author: Demonstrandum <moi@knutsen.co>
Date:   Sun,  8 Mar 2020 17:50:21 +0000

Added Oxford English Dictionary command interface.

Diffstat:
MHELP.md | 1+
Alib/api/oxford.ts | 29+++++++++++++++++++++++++++++
Mlib/api/urban.ts | 7++++++-
Mlib/api/web.ts | 27+++++++++++----------------
Mlib/default.ts | 3+++
Mlib/extensions.ts | 4++++
Mlib/main.ts | 111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mlib/utils.ts | 2+-
8 files changed, 151 insertions(+), 33 deletions(-)

diff --git a/HELP.md b/HELP.md @@ -38,6 +38,7 @@ - `!urban [slang]` — Looks up a piece of slang in the _Urban Dictionary_. - `!search [web-search-terms]` — Performs a web-search and returns the most appropriate URL found. - `!image [image-search-terms]` — Searches for images specified by the terms given, and send a link to the most relevant one. +- `!news [news-search-term]` — Sends you the most relevant new on the specified topic area. - `!youtube [youtube-search-terms]` — Searches for and returns a relevant _YouTube_ video. - `!cron` — Run commands repeatedly based on some timer (Google cron syntax for more info): - `!cron [minute] [hour] [day-of-month] [month] [day-of-week] ![command] <...>` — runs a command (with or without arguments) repeatedly as specified by the schedule signature. diff --git a/lib/api/oxford.ts b/lib/api/oxford.ts @@ -0,0 +1,29 @@ +import unirest from 'unirest'; + +type Options = { + word : string, + lang : string, + id : string, + key : string +} + +export const oed_lookup = (options : Options) => new Promise((resolve, reject) => { + console.log('Searching Oxford English Dictionary, with options: ', options); + + const url = 'https://od-api.oxforddictionaries.com:443/api/v2/entries'; + + const word = options.word.toLowerCase(); + const req = unirest('GET', `${url}/${options.lang}/${word}`); + + req.headers({ + "app_id": options.id, + "app_key": options.key + }); + + req.end(res => { + if (res.error) return reject(res.error); + return resolve(res.body); + }); +}); + +export default oed_lookup; diff --git a/lib/api/urban.ts b/lib/api/urban.ts @@ -1,6 +1,11 @@ import unirest from 'unirest'; -export const urban_search = options => new Promise((resolve, reject) => { +type Options = { + query : string, + key : string +} + +export const urban_search = (options : Options) => new Promise((resolve, reject) => { console.log('Searching Urban Dictionary, with options: ', options); const url = 'https://mashape-community-urban-dictionary.p.rapidapi.com/define'; diff --git a/lib/api/web.ts b/lib/api/web.ts @@ -1,23 +1,18 @@ import unirest from 'unirest'; -export const web_search = options => new Promise((resolve, reject) => { +type Options = { + query : string, + type : 'image' | 'web' | 'news', + key : string +} + +export const web_search = (options : Options) => new Promise((resolve, reject) => { console.log('Searching the web, with options: ', options); - let api = 'WebSearchAPI'; - switch (options.type) { - case 'image': - api = 'ImageSearchAPI'; - break; - case 'web': - api = 'WebSearchAPI'; - break; - case 'news': - api = 'NewsSearchAPI'; - break; - } - const url = `https://contextualwebsearch-websearch-v1.p.rapidapi.com/api/Search/${api}`; - - const req = unirest('GET', url); + const api = `${options.type.capitalize()}SearchAPI`; + const url = 'https://contextualwebsearch-websearch-v1.p.rapidapi.com/api/Search'; + + const req = unirest('GET', `${url}/${api}`); req.query({ "autoCorrect": "false", diff --git a/lib/default.ts b/lib/default.ts @@ -6,6 +6,7 @@ export default { name: "Simp'O'Matic", tag: "#1634", permissions: 8, + lang: 'en', commands: { prefix: '!', @@ -18,7 +19,9 @@ export default { 'yt': 'youtube', 'd': 'define', 'oed': 'define', + 'oxford': 'define', 'ud': 'urban', + 'u': 'urban', 'blacklist': 'ignore', 'whitelist': 'ignore whitelist', 'w': 'weather', diff --git a/lib/extensions.ts b/lib/extensions.ts @@ -26,12 +26,16 @@ Array.prototype.mut_map = function (f) { // String Extensions: interface String { squeeze() : string + capitalize() : string } String.prototype.squeeze = function () { return this.split(/[ ]+/).join(' '); }; +String.prototype.capitalize = function () { + return this.charAt(0).toUpperCase() + this.slice(1); +} // Number Extensions: interface Number { diff --git a/lib/main.ts b/lib/main.ts @@ -12,6 +12,7 @@ import './extensions'; import { deep_merge, pp, compile_match, export_config } from './utils'; import DEFAULT_CONFIG from './default'; import web_search from './api/web'; +import oed_lookup from './api/oxford'; import urban_search from './api/urban'; // Anything that hasn't been defined in `bot.json` @@ -80,7 +81,7 @@ export class SimpOMatic { console.log('Received command: ', [command, args]); switch (command) { - case "ping": { + case 'ping': { message.reply("PONGGERS!"); break; } case 'help': { @@ -88,18 +89,18 @@ export class SimpOMatic { for (const msg of HELP_MESSAGES) message.channel.send(msg); break; - } case "id": { + } case 'id': { const reply = `User ID: ${message.author.id} Author: ${message.author} Message ID: ${message.id}`.squeeze(); console.log(`Replied: ${reply}`); message.reply(reply); break; - } case "search": { + } case 'search': { const query = args.join(' '); web_search({ - type: "search", + type: 'web', query, key: SECRETS.rapid.key }).then((res: object) => { @@ -107,14 +108,15 @@ export class SimpOMatic { message.reply('No such results found.'); return; } - message.reply(`Web search for ‘${query}’, found: ${res['value'][0].url}`); + message.reply(`Web search for ‘${query}’, + found: ${res['value'][0].url}`); }).catch(e => message.reply(`Error fetching results:\n${e}`)); break; - } case "image": { + } case 'image': { const query = args.join(' '); web_search({ - type: "image", + type: 'image', query, key: SECRETS.rapid.key }).then(res => { @@ -122,34 +124,113 @@ export class SimpOMatic { message.reply('No such images found.'); return; } - message.reply(`Image found for ‘${query}’: ${res['value'][0].url}`); + message.reply(`Image found for ‘${query}’: + ${res['value'][0].url}`); }).catch(e => message.reply(`Error fetching image:\n${e}`)); break; - } case "urban": { + } case 'define': { + message.reply('Looking in the Oxford English Dictionary...'); const query = args.join(' '); + + const nasty_reply = `Your word (‘${query}’) is nonsense, either \ + that or they've forgotten to index it. + I'll let you decide.`.squeeze(); + + oed_lookup({ + word: query, + lang: CONFIG.lang, + id: SECRETS.oxford.id, + key: SECRETS.oxford.key + }).then(res => { + let msg = `Definition for ‘${query}’, yielded:\n`; + + if (!res['results'] + || res['results'].length == 0 + || !res['results'][0].lexicalEntries + || res['results'][0].lexicalEntries.length == 0 + || res['results'][0].lexicalEntries[0].entries.length == 0 + || res['results'][0].lexicalEntries[0].entries[0].senses.length == 0) { + message.reply(nasty_reply); + return; + } + + const entry = res['results'][0].lexicalEntries[0]; + const senses = entry.entries[0].senses; + console.log('Senses:', pp(senses)); + for (const sense of Object.values(senses) as any) { + let sense_msg = ""; + if (!!sense.definitions && sense.definitions.length > 0) { + for (const definition + of Object.values(sense.definitions) as any) { + sense_msg += ` Defined as:\n> ${definition.capitalize()}\n`; + } + } + if (!!sense.synonyms && sense.synonyms.length > 0) { + const synonyms = sense.synonyms + .map(s => `‘${s.text}’`) + .join(', '); + sense_msg += ` Synonyms include: ${synonyms}\n`; + } + if (sense_msg.length > 0) { + msg += "In the sense:\n" + msg += sense_msg; + } + } + const prons = Object.values(entry.pronunciations) as any; + if (prons.length > 0) { + msg += "Pronunciations:\n" + for (const pron of prons) { + msg += ` Dialects of ${pron.dialects.join(', ')}:\n`; + msg += ` ${pron.phoneticNotation}: [${pron.phoneticSpelling}]\n`; + if (pron.audioFile) { + msg += ` Audio file: ${pron.audioFile}\n`; + const attach = new MessageAttachment(pron.audioFile); + attach.name = pron.audioFile.split('/').slice(-1)[0]; + message.channel.send(attach); + } + } + } + message.channel.send(msg); + }).catch(e => { + if (e.status == 404) { + message.channel.send(nasty_reply); + } else { + message.channel.send(`Error getting definition:\n${e}`); + } + }); + break; + } case 'urban': { + const query = args.join(' '); + message.reply('Searching Urban Dictionary...'); urban_search({ query, key: SECRETS.rapid.key }).then(res => { if (res['list'].length === 0) { - message.reply(`Congratulations, not even Urban \ + message.channel.send(`Congratulations, not even Urban \ Dictionary knows what you're trying to say.`.squeeze()); return; } const entry = res['list'][0]; const def = entry.definition.replace(/\[|\]/g, ''); - message.reply(`Urban Dictionary defines \ - ‘${query}’, as:\n> ${def}`.squeeze()); + message.channel.send(`**Urban Dictionary** defines \ + ‘${query}’, as:\n>>> ${def.trim()}`.squeeze()); + + let example = entry.example; + if (!!example || example.length > 0) { + example = example.replace(/\[|\]/g, ''); + message.channel.send(`\n**Example**:\n>>> ${example}`); + } message.channel.send(`Link: ${entry.permalink}`); }).catch(e => message.reply(`Error fetching definition:\n${e}`)); break; - } case "milkies": { + } case 'milkies': { message.reply(`${(4 + Math.random() * 15).round_to(3)} gallons \ of milkies have been deposited in your mouth.`.squeeze()); break; - } case "say": {2 + } case 'say': {2 message.reply(`Me-sa says: “${args.join(' ')}”`); break; - } case "export": { + } case 'export': { let export_string = export_config(CONFIG, {}); if (export_string.length > 1980) { export_string = export_config(CONFIG, { ugly: true }); diff --git a/lib/utils.ts b/lib/utils.ts @@ -13,7 +13,7 @@ export const type: (obj: any) => string = (global => obj => export const pp = o => inspect(o, { colors: true, showHidden: false, - depth: 8 + depth: 50 }); export const deep_merge_pair = (target, source) => {