Simp-O-Matic

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

commit 1888e2c038a74ac96b197b3f6c08cb74aea9aab9
parent bafdffa5708a37f1b11f4e8563c771008b6f7582
Author: Demonstrandum <moi@knutsen.co>
Date:   Wed, 18 Mar 2020 15:29:21 +0000

Act on rules, but no way to add new ones.

Diffstat:
MHELP.md | 3+++
Mlib/api/contextual.ts | 54+++++++++++++++++++++++++++---------------------------
Mlib/api/google.ts | 82++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mlib/api/oxford.ts | 32++++++++++++++++----------------
Mlib/api/pastebin.ts | 30+++++++++++++++---------------
Mlib/api/urban.ts | 32++++++++++++++++----------------
Mlib/api/youtube.ts | 98++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mlib/api/yt_scrape.ts | 26+++++++++++++-------------
Mlib/default.ts | 238++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mlib/extensions.ts | 134++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mlib/format_oed.ts | 126++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mlib/main.ts | 23+++++++++++++++--------
Mlib/utils.ts | 138++++++++++++++++++++++++++++++++++++++++----------------------------------------
13 files changed, 515 insertions(+), 501 deletions(-)

diff --git a/HELP.md b/HELP.md @@ -74,6 +74,9 @@ - `!wikipedia` **〈not impl.〉** — Search through Wikipedia, returning the most relevant wiki-link. - `!translate <language> [phrase]` **〈not impl.〉** — Translate a phrase from a language (if none specified, it will auto-detect). - `!wolfram` **〈not impl.〉** — Query Wolfram|Alpha. +- `!weather` — Check the weather: + - `!weather set [location]` — sets your weather location. + - `!weather <location>` — gives you the weather in a certain location, if location is left blank, it will either give you the weather in the default location, or in the area you `set` previously. - `!say [phrase]` — Repeats what you told it to say. - `!milkies` — In case you're feeling thirsty... - `!cowsay <options> [phrase]` **〈not impl.〉** — Make a cow say something, using Unix-like command line arguments. diff --git a/lib/api/contextual.ts b/lib/api/contextual.ts @@ -1,36 +1,36 @@ import unirest from 'unirest'; type Options = { - query : string, - type : 'image' | 'web' | 'news', - key : string + 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); - - 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", - "pageNumber": "1", - "pageSize": "10", - "q": options.query, - "safeSearch": "false" - }); - - req.headers({ - "x-rapidapi-host": "contextualwebsearch-websearch-v1.p.rapidapi.com", - "x-rapidapi-key": options.key - }); - - req.end(res => { - if (res.error) return reject(res.error); - return resolve(res.body); - }); + console.log('Searching the web, with options: ', options); + + 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", + "pageNumber": "1", + "pageSize": "10", + "q": options.query, + "safeSearch": "false" + }); + + req.headers({ + "x-rapidapi-host": "contextualwebsearch-websearch-v1.p.rapidapi.com", + "x-rapidapi-key": options.key + }); + + req.end(res => { + if (res.error) return reject(res.error); + return resolve(res.body); + }); }); export default web_search; diff --git a/lib/api/google.ts b/lib/api/google.ts @@ -1,22 +1,22 @@ import { google } from 'googleapis'; type CSE = { - kind: 'image' | 'web', - key: string, - id: string, - query: string + kind: 'image' | 'web', + key: string, + id: string, + query: string }; // Cache grows from the bottom, and deletes from the top. // i.e. old cached items get deleted, since they're unpopular. const CACHE_SIZE = 40; const CACHE = { - 'aaron obese': "https://assets3.thrillist.com/v1/image/2765017/size/tmg-article_tall;jpeg_quality=20.jpg\n>>> Aaron obese xD, shut up instgen...", - 'druggie': "https://vignette.wikia.nocookie.net/sausage-party-recipe-book/images/e/e7/Druggie.png/revision/latest/top-crop/width/360/height/450?cb=20170212165040\n>>> Hurr durr, arron's a shroomer lmao.", - 'aaron fish': "https://i.pinimg.com/280x280_RS/fa/b5/96/fab5962b97d464781f65952b6b63e4a0.jpg\n>>> arron fish? arron fish. Blub blub.", - 'sammy obese': "https://s3fs.bestfriends.org/s3fs-public/news/15/09/04/SIJenPettingSammy6919.jpg\n>>> sammy is a cute skinny twink, stfu.", - 'james obese': "https://i.dailymail.co.uk/i/pix/2014/02/18/article-2562421-1B9E557700000578-9_634x468.jpg\n>>> james very obese, yes, very original...", - 'aaron penis': "http://m.quickmeme.com/img/ea/eaf8c78ae55815cc7786b1596f9a9767fc3c42b60efff8a2455150e4d0eb37b0.jpg\n>>> aaron's schlong is actually huge. shroomer schlong." + 'aaron obese': "https://assets3.thrillist.com/v1/image/2765017/size/tmg-article_tall;jpeg_quality=20.jpg\n>>> Aaron obese xD, shut up instgen...", + 'druggie': "https://vignette.wikia.nocookie.net/sausage-party-recipe-book/images/e/e7/Druggie.png/revision/latest/top-crop/width/360/height/450?cb=20170212165040\n>>> Hurr durr, arron's a shroomer lmao.", + 'aaron fish': "https://i.pinimg.com/280x280_RS/fa/b5/96/fab5962b97d464781f65952b6b63e4a0.jpg\n>>> arron fish? arron fish. Blub blub.", + 'sammy obese': "https://s3fs.bestfriends.org/s3fs-public/news/15/09/04/SIJenPettingSammy6919.jpg\n>>> sammy is a cute skinny twink, stfu.", + 'james obese': "https://i.dailymail.co.uk/i/pix/2014/02/18/article-2562421-1B9E557700000578-9_634x468.jpg\n>>> james very obese, yes, very original...", + 'aaron penis': "http://m.quickmeme.com/img/ea/eaf8c78ae55815cc7786b1596f9a9767fc3c42b60efff8a2455150e4d0eb37b0.jpg\n>>> aaron's schlong is actually huge. shroomer schlong." }; // TODO: Reject results if they're from: @@ -25,37 +25,37 @@ const CACHE = { // These web-places already have commands given to them. const web_search = (param : CSE) => new Promise((resolve, reject) => { - const cache_keys = Object.keys(CACHE); - // Retrieve cached query. - if (param.query in CACHE) { - return resolve(`${CACHE[param.query]} (cached response)`) - } else if (cache_keys.length > CACHE_SIZE) { - // Delete a few, so we can delete less frequently. - delete CACHE[cache_keys[0]]; - delete CACHE[cache_keys[1]]; - delete CACHE[cache_keys[2]]; - } - - const cs = google.customsearch('v1'); - - cs.cse.list({ - auth: param.key, - cx: param.id, - q: param.query, - searchType: (param.kind === 'web') ? undefined : param.kind, - start: 0, - num: 1 - }).then(res => { - if (!res.data || !res.data.items || res.data.items.length === 0) - return reject('No such results found.') - - const item = res.data.items[0]; - const answer = `${item.link}\n>>> ${item.title}`; - // Cache this query - CACHE[param.query] = answer; - return resolve(answer); - }).catch(e => - reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); + const cache_keys = Object.keys(CACHE); + // Retrieve cached query. + if (param.query in CACHE) { + return resolve(`${CACHE[param.query]} (cached response)`) + } else if (cache_keys.length > CACHE_SIZE) { + // Delete a few, so we can delete less frequently. + delete CACHE[cache_keys[0]]; + delete CACHE[cache_keys[1]]; + delete CACHE[cache_keys[2]]; + } + + const cs = google.customsearch('v1'); + + cs.cse.list({ + auth: param.key, + cx: param.id, + q: param.query, + searchType: (param.kind === 'web') ? undefined : param.kind, + start: 0, + num: 1 + }).then(res => { + if (!res.data || !res.data.items || res.data.items.length === 0) + return reject('No such results found.') + + const item = res.data.items[0]; + const answer = `${item.link}\n>>> ${item.title}`; + // Cache this query + CACHE[param.query] = answer; + return resolve(answer); + }).catch(e => + reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); }); export default web_search; diff --git a/lib/api/oxford.ts b/lib/api/oxford.ts @@ -1,29 +1,29 @@ import unirest from 'unirest'; type Options = { - word : string, - lang : string, - id : string, - key : string + 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); + console.log('Searching Oxford English Dictionary, with options: ', options); - const url = 'https://od-api.oxforddictionaries.com:443/api/v2/entries'; + const url = 'https://od-api.oxforddictionaries.com:443/api/v2/entries'; - const word = options.word.toLowerCase(); - const req = unirest('GET', `${url}/${options.lang}/${word}`); + const word = options.word.toLowerCase(); + const req = unirest('GET', `${url}/${options.lang}/${word}`); - req.headers({ - "app_id": options.id, - "app_key": options.key - }); + req.headers({ + "app_id": options.id, + "app_key": options.key + }); - req.end(res => { - if (res.error) return reject(res.error); - return resolve(res.body); - }); + req.end(res => { + if (res.error) return reject(res.error); + return resolve(res.body); + }); }); export default oed_lookup; diff --git a/lib/api/pastebin.ts b/lib/api/pastebin.ts @@ -8,23 +8,23 @@ export const pastebin_url = `https://pastebin.com/${PASTE_ID}`; paste.setDevKey(process.env['PASTEBIN_KEY']); export const pastebin_latest = () => new Promise((resolve, reject) => { - paste.get(PASTE_ID, (succ, res) => { - if (!succ) - return reject('Error getting paste.'); - resolve(JSON.parse(res)); - }); + paste.get(PASTE_ID, (succ, res) => { + if (!succ) + return reject('Error getting paste.'); + resolve(JSON.parse(res)); + }); }); export const pastebin_update = async function (stringified : string) { - await paste.login(PASTE_USER, PASTE_PASS, async function (succ, res) { - if (!succ) - return Promise.reject(console.log('Could not log in.')); + await paste.login(PASTE_USER, PASTE_PASS, async function (succ, res) { + if (!succ) + return Promise.reject(console.log('Could not log in.')); - return await paste.edit(PASTE_ID, { - contents: stringified - }, async function (succ, _res) { - if (!succ) - return console.log('Error updating paste...'); - }); - }); + return await paste.edit(PASTE_ID, { + contents: stringified + }, async function (succ, _res) { + if (!succ) + return console.log('Error updating paste...'); + }); + }); }; diff --git a/lib/api/urban.ts b/lib/api/urban.ts @@ -1,30 +1,30 @@ import unirest from 'unirest'; type Options = { - query : string, - key : string + query : string, + key : string } export const urban_search = (options : Options) => new Promise((resolve, reject) => { - console.log('Searching Urban Dictionary, with options: ', options); + console.log('Searching Urban Dictionary, with options: ', options); - const url = 'https://mashape-community-urban-dictionary.p.rapidapi.com/define'; + const url = 'https://mashape-community-urban-dictionary.p.rapidapi.com/define'; - const req = unirest('GET', url); + const req = unirest('GET', url); - req.query({ - "term": options.query - }); + req.query({ + "term": options.query + }); - req.headers({ - "x-rapidapi-host": "mashape-community-urban-dictionary.p.rapidapi.com", - "x-rapidapi-key": options.key - }); + req.headers({ + "x-rapidapi-host": "mashape-community-urban-dictionary.p.rapidapi.com", + "x-rapidapi-key": options.key + }); - req.end(res => { - if (res.error) return reject(res.error); - return resolve(res.body); - }); + req.end(res => { + if (res.error) return reject(res.error); + return resolve(res.body); + }); }); export default urban_search; diff --git a/lib/api/youtube.ts b/lib/api/youtube.ts @@ -11,66 +11,66 @@ import '../extensions'; const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; type YTSearch = { - key: string, - query: string + key: string, + query: string }; const web_search = (param: YTSearch) => new Promise((resolve, reject) => { - const yt = google.youtube('v3'); - const auth = new OAuth2({ - clientId: process.env['GOOGLE_OAUTH_ID'], - clientSecret: process.env['GOOGLE_OAUTH_SECRET'], - redirectUri: 'https://google.com/' - }); - // const auth_url = auth.generateAuthUrl({ - // access_type: 'offline', - // scope: SCOPES - // }); - // console.log('Authorize this app by visiting this url: ', auth_url); - auth.getToken(process.env['GOOGLE_PERSONAL_CODE']).then(code => { - auth.setCredentials(code.tokens); - yt.search.list({ - q: param.query, - maxResults: 1, - auth: auth, - part: 'snippet' - }).then(res => { - if (!res.data || !res.data.items || res.data.items.length === 0) - return reject('No such results found.') + const yt = google.youtube('v3'); + const auth = new OAuth2({ + clientId: process.env['GOOGLE_OAUTH_ID'], + clientSecret: process.env['GOOGLE_OAUTH_SECRET'], + redirectUri: 'https://google.com/' + }); + // const auth_url = auth.generateAuthUrl({ + // access_type: 'offline', + // scope: SCOPES + // }); + // console.log('Authorize this app by visiting this url: ', auth_url); + auth.getToken(process.env['GOOGLE_PERSONAL_CODE']).then(code => { + auth.setCredentials(code.tokens); + yt.search.list({ + q: param.query, + maxResults: 1, + auth: auth, + part: 'snippet' + }).then(res => { + if (!res.data || !res.data.items || res.data.items.length === 0) + return reject('No such results found.') - const video = res.data.items[0]; - const id = video.id.videoId; + const video = res.data.items[0]; + const id = video.id.videoId; - yt.videos.list({ - part: 'statistics', id - }).then(vid_res => { - const title = video.snippet.title; - const { viewCount: views, - likeCount: likes, - dislikeCount: dislikes } = vid_res.data.items[0].statistics; + yt.videos.list({ + part: 'statistics', id + }).then(vid_res => { + const title = video.snippet.title; + const { viewCount: views, + likeCount: likes, + dislikeCount: dislikes } = vid_res.data.items[0].statistics; - const url = `https://youtu.be/${id}/`; - const by = video.snippet.channelTitle; - console.log(video); + const url = `https://youtu.be/${id}/`; + const by = video.snippet.channelTitle; + console.log(video); - return resolve(`${url}\n> ${title} | ${views} views | \ - :+1: ${likes} — :-1: ${dislikes} \nby: ${by}.`.squeeze()); - }).catch(e => - reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); - }).catch(e => - reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); - }).catch(err => { - console.log('Error with code:', err); - reject('Token probably expired, i.e. logged out.\n' - + '```\n' + err.toString() + '\n```'); - }); + return resolve(`${url}\n> ${title} | ${views} views | \ + :+1: ${likes} — :-1: ${dislikes} \nby: ${by}.`.squeeze()); + }).catch(e => + reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); + }).catch(e => + reject(`No results, or API capped...\n\`\`\`\n${e}\n\`\`\``)); + }).catch(err => { + console.log('Error with code:', err); + reject('Token probably expired, i.e. logged out.\n' + + '```\n' + err.toString() + '\n```'); + }); }); export default web_search; web_search({ - key: process.env['GOOGLE_API_KEY'], - query: 'cat videos' + key: process.env['GOOGLE_API_KEY'], + query: 'cat videos' }).then(res => { - console.log(res); + console.log(res); }).catch(console.log); diff --git a/lib/api/yt_scrape.ts b/lib/api/yt_scrape.ts @@ -2,24 +2,24 @@ const search = require('scrape-youtube'); import '../extensions'; type YTSearch = { - query: string + query: string }; const yt_search = (params: YTSearch) => new Promise((resolve, reject) => { - search(params.query, { - limit: 2, - type: 'video' - }).then(res => { - if (!res || res.length === 0) { - return reject('No YouTube results found.'); - } + search(params.query, { + limit: 2, + type: 'video' + }).then(res => { + if (!res || res.length === 0) { + return reject('No YouTube results found.'); + } - const { channel: by, title, views, - upload_date, link: url } = res[0]; + const { channel: by, title, views, + upload_date, link: url } = res[0]; - return resolve(`${url}\n> ${title} | ${views.to_metric()} views | \ - uploaded ${upload_date} | by: ${by}.`.squeeze()); - }); + return resolve(`${url}\n> ${title} | ${views.to_metric()} views | \ + uploaded ${upload_date} | by: ${by}.`.squeeze()); + }); }); export default yt_search; diff --git a/lib/default.ts b/lib/default.ts @@ -3,125 +3,129 @@ /// laid out. All fields are accounted for here. export default { - name: "Simp'O'Matic", - tag: "#1634", - permissions: 8, - lang: 'en', + name: "Simp'O'Matic", + tag: "#1634", + permissions: 8, + lang: 'en', - weather_locations: { - '541761315887120399': 'Moscow' - }, - commands: { - prefix: '!', - max_history: 40, - not_understood: "Command not understood", - aliases: { - 'img': 'image', - 'i': 'image', - 'h': 'help', - 's': 'search', - 'web': 'search', - 'g': 'search', - 'google': 'search', - 'bing': 'search', - 'yt': 'youtube', - 'y': 'youtube', - 'd': 'define', - 'def': 'define', - 'oed': 'define', - 'oxford': 'define', - 'ud': 'urban', - 'u': 'urban', - 'blacklist': 'ignore', - 'whitelist': 'ignore whitelist', - 'w': 'weather', - 'reply': 'respond', - 'reject': 'delete', - 'wa': 'wolfram', - 'wolf': 'wolfram', - 'toilet': 'figlet', - 'wiki': 'wikipedia', - 'aliases': 'alias', - 'boomerfy': 'boomer', - 'mocking': 'mock', - 'pull': 'fork', - 'git': 'github', - 'bug': 'issue', - 'source': 'github', - 'save': 'export' - }, - }, + weather_locations: { + '541761315887120399': 'Moscow' + }, + commands: { + prefix: '!', + max_history: 40, + not_understood: "Command not understood", + aliases: { + 'img': 'image', + 'i': 'image', + 'h': 'help', + 's': 'search', + 'web': 'search', + 'g': 'search', + 'google': 'search', + 'bing': 'search', + 'yt': 'youtube', + 'y': 'youtube', + 'd': 'define', + 'def': 'define', + 'oed': 'define', + 'oxford': 'define', + 'ud': 'urban', + 'u': 'urban', + 'blacklist': 'ignore', + 'whitelist': 'ignore whitelist', + 'w': 'weather', + 'reply': 'respond', + 'reject': 'delete', + 'wa': 'wolfram', + 'wolf': 'wolfram', + 'toilet': 'figlet', + 'wiki': 'wikipedia', + 'aliases': 'alias', + 'boomerfy': 'boomer', + 'mocking': 'mock', + 'pull': 'fork', + 'git': 'github', + 'bug': 'issue', + 'source': 'github', + 'save': 'export' + }, + }, - rules: { - // Below are the different kinds of _rules_. - respond: [ - { - match: "/^\\s*thanks.*\\s*$/i", - response: 'Obama.' - }, - ], - reject: [ - { - match: "/\\.{4,}/", - response: null - }, - ], - replace: [ // Message editing functionality not a thing yet... - { - match: "/tbh/i", - response: 'desu' - }, - ], - // Blacklist (initially everyone can do everything, - // except for those listed specifically on this list). - blacklist: { - channels: [ - 'music', 'news' - ], - users: { - // Should all be numbers/hashes, this one is bogus: - "instcel": { - commands: true, - commands_elevated: false, - speech: true, - }, - // For real this time: - // -> `accelarion#0764` - // a.k.a. instcel, - // a.k.a. instgen, - // a.k.a. installgentoo. - "409461942495871016": { - commands: true, - commands_elevated: false, - speech: true - } - }, - groups: { - // Should all be numbers/hashes, these are bogus. - "obese": { - commands_elevated: false, - }, - "bpd": { - commands: false, - } - }, - }, + rules: { + // Below are the different kinds of _rules_. + respond: [ + { + match: "/^\\s*thanks.*\\s*$/i", + response: 'Obama.' + }, + { + match: "/[^\p{L}\p{N}]bot[^\p{L}\p{N}]/i", + respond: "The hell you sayn' about bots?" + } + ], + reject: [ + { + match: "/\\.{4,}/", + response: null + }, + ], + replace: [ // Message editing functionality not a thing yet... + { + match: "/tbh/i", + response: 'desu' + }, + ], + // Blacklist (initially everyone can do everything, + // except for those listed specifically on this list). + blacklist: { + channels: [ + 'music', 'news' + ], + users: { + // Should all be numbers/hashes, this one is bogus: + "instcel": { + commands: true, + commands_elevated: false, + speech: true, + }, + // For real this time: + // -> `accelarion#0764` + // a.k.a. instcel, + // a.k.a. instgen, + // a.k.a. installgentoo. + "409461942495871016": { + commands: true, + commands_elevated: false, + speech: true + } + }, + groups: { + // Should all be numbers/hashes, these are bogus. + "obese": { + commands_elevated: false, + }, + "bpd": { + commands: false, + } + }, + }, - // In case you blacklist @everyone or something, - // you can override completely, to obtain all permissions just - // by putting them on the whitelist. - whitelist: { - users: [ - // Dr. Henry Kissinger#5457 - "265958795254038535", - // Danny#1986 - "541761315887120399" - ], - groups: [ - // Will all be numbers/hashes too. - "bourgeoisie" - ] - } - } + // In case you blacklist @everyone or something, + // you can override completely, to obtain all permissions just + // by putting them on the whitelist. + whitelist: { + users: [ + // Dr. Henry Kissinger#5457 + "265958795254038535", + // Danny#1986 + "541761315887120399" + ], + groups: [ + // Will all be numbers/hashes too. + "bourgeoisie" + ] + } + } } diff --git a/lib/extensions.ts b/lib/extensions.ts @@ -1,53 +1,53 @@ // Global Extensions: declare global { - interface Array<T> { - head(): T - tail(): Array<T> - first(): T - last(off? : number | undefined): T - get(i : number): T - unique(): Array<T> - squeeze(): Array<T> - each(callbackfn : (value : T, index : number, array : T[]) => void, thisArg? : T): void - mut_unique(): Array<T> - mut_map(f: (T) => any): Array<any> - } - - interface String { - squeeze(): string - capitalize(): string - leading_space(): string - head(): string - tail(): string - first(): string - last(off? : number): string - } - - interface Number { - round_to(dp: number): number - to_metric(figures): string - } + interface Array<T> { + head(): T + tail(): Array<T> + first(): T + last(off? : number | undefined): T + get(i : number): T + unique(): Array<T> + squeeze(): Array<T> + each(callbackfn : (value : T, index : number, array : T[]) => void, thisArg? : T): void + mut_unique(): Array<T> + mut_map(f: (T) => any): Array<any> + } + + interface String { + squeeze(): string + capitalize(): string + leading_space(): string + head(): string + tail(): string + first(): string + last(off? : number): string + } + + interface Number { + round_to(dp: number): number + to_metric(figures): string + } } // Array Extensions: Array.prototype.head = function () { - return this[0]; + return this[0]; }; Array.prototype.first = Array.prototype.head; Array.prototype.tail = function () { - return this.slice(1); + return this.slice(1); }; Array.prototype.last = function (off=0) { - return this[this.length - 1 - off]; + return this[this.length - 1 - off]; }; Array.prototype.get = function (i) { return this[i] }; Array.prototype.unique = function () { - return this.filter((e, i) => this.indexOf(e) === i) + return this.filter((e, i) => this.indexOf(e) === i) }; Array.prototype.squeeze = function () { return this.filter(e => !!e); }; @@ -55,32 +55,32 @@ Array.prototype.squeeze = function () { return this.filter(e => !!e); }; Array.prototype.each = Array.prototype.forEach; Array.prototype.mut_unique = function () { - const uniq = this.unique(); - Object.assign(this, uniq); - this.splice(uniq.length); - return this; + const uniq = this.unique(); + Object.assign(this, uniq); + this.splice(uniq.length); + return this; }; Array.prototype.mut_map = function (f) { - for (const i in this) { - this[i] = f(this[i]); - } - return this; + for (const i in this) { + this[i] = f(this[i]); + } + return this; }; // String Extensions: String.prototype.squeeze = function () { - return this - .replace(/[\t\s]+/g, ' ') - .replace(/\n[\t\s]/g, '\n'); + return this + .replace(/[\t\s]+/g, ' ') + .replace(/\n[\t\s]/g, '\n'); }; String.prototype.leading_space = function () { - return this.replace(/\n[ ]([^ ]+)/g, '\n$1'); + return this.replace(/\n[ ]([^ ]+)/g, '\n$1'); }; String.prototype.capitalize = function () { - return this.charAt(0).toUpperCase() + this.slice(1); + return this.charAt(0).toUpperCase() + this.slice(1); }; String.prototype.head = Array.prototype.head as any; @@ -90,42 +90,42 @@ String.prototype.last = Array.prototype.last as any; // Number Extensions: Number.prototype.round_to = function (dp : number) { - const exp = 10 ** dp; - return Math.round(this.valueOf() * exp) / exp; + const exp = 10 ** dp; + return Math.round(this.valueOf() * exp) / exp; }; const SI_EXTENSIONS = [ - { value: 1, symbol: "" }, - { value: 1E3, symbol: "k" }, - { value: 1E6, symbol: "M" }, - { value: 1E9, symbol: "G" }, - { value: 1E12, symbol: "T" }, - { value: 1E15, symbol: "P" }, - { value: 1E18, symbol: "E" } + { value: 1, symbol: "" }, + { value: 1E3, symbol: "k" }, + { value: 1E6, symbol: "M" }, + { value: 1E9, symbol: "G" }, + { value: 1E12, symbol: "T" }, + { value: 1E15, symbol: "P" }, + { value: 1E18, symbol: "E" } ]; Number.prototype.to_metric = function (figures) { - let i = SI_EXTENSIONS.length - 1; - for (; i > 0; --i) - if (this >= SI_EXTENSIONS[i].value) - break; - - return (this.valueOf() / SI_EXTENSIONS[i].value) - .toFixed(figures) - .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1") - + SI_EXTENSIONS[i].symbol; + let i = SI_EXTENSIONS.length - 1; + for (; i > 0; --i) + if (this >= SI_EXTENSIONS[i].value) + break; + + return (this.valueOf() / SI_EXTENSIONS[i].value) + .toFixed(figures) + .replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1") + + SI_EXTENSIONS[i].symbol; }; // Discord Extensions: declare module 'discord.js' { - interface Message { - answer(...args: any): void - } + interface Message { + answer(...args: any): void + } } import { Message } from 'discord.js'; Message.prototype.answer = function (...args) { - return this.channel.send(`${this.author}, ${args[0]}`, - ...(args.slice(1))); + return this.channel.send(`${this.author}, ${args[0]}`, + ...(args.slice(1))); }; diff --git a/lib/format_oed.ts b/lib/format_oed.ts @@ -3,69 +3,69 @@ import { pp } from './utils'; // Mmm... spaghetti... export default (res, message) => { - let msg = `Definition for ‘${res.word}’, yielded:\n`; - let has_sent_audio = false; + let msg = `Definition for ‘${res.word}’, yielded:\n`; + let has_sent_audio = false; - const lex_entries = res['results'][0].lexicalEntries; - let entry_n = 1; - for (const lex_entry of lex_entries) { - if (lex_entries.length > 1) { - msg += `\nLexical Entry №${entry_n}:\n` - entry_n += 1; - } - console.log('Lex entry:', pp(lex_entries)) - for (const entry of Object.values(lex_entry.entries)) { - const senses = entry['senses']; + const lex_entries = res['results'][0].lexicalEntries; + let entry_n = 1; + for (const lex_entry of lex_entries) { + if (lex_entries.length > 1) { + msg += `\nLexical Entry №${entry_n}:\n` + entry_n += 1; + } + console.log('Lex entry:', pp(lex_entries)) + for (const entry of Object.values(lex_entry.entries)) { + const senses = entry['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 (${lex_entry.lexicalCategory.text.toLowerCase()}):\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.trim().length > 0) { - msg += "\nIn the sense:\n" - msg += sense_msg; - } - } - const etys = entry['etymologies']; - if (!!etys && etys.length > 0) { - msg += '\nEtymology:\n '; - msg += etys.join(';\n '); - msg += '\n'; - } - } - if (!!lex_entry.pronunciations && !has_sent_audio) { - const prons = Object.values(lex_entry.pronunciations) as any; - if (!!prons && prons.length > 0) { - msg += "\nPronunciations:\n" - for (const pron of prons) { - if (!!pron.dialects) { - const dialects = Object.values(pron.dialects); - msg += ` Dialects of ${dialects.join(', ')}:\n`; - } - msg += ` ${pron.phoneticNotation}: [${pron.phoneticSpelling}]\n`; - if (pron.audioFile) { - msg += ` Audio file: ${pron.audioFile}\n`; - has_sent_audio = !has_sent_audio; - const attach = new Attachment( - pron.audioFile, - pron.audioFile.split('/').slice(-1)[0] - ); - message.channel.send('', attach); - } - } - } - } - } - console.log('Became:', msg); - return msg; + 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 (${lex_entry.lexicalCategory.text.toLowerCase()}):\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.trim().length > 0) { + msg += "\nIn the sense:\n" + msg += sense_msg; + } + } + const etys = entry['etymologies']; + if (!!etys && etys.length > 0) { + msg += '\nEtymology:\n '; + msg += etys.join(';\n '); + msg += '\n'; + } + } + if (!!lex_entry.pronunciations && !has_sent_audio) { + const prons = Object.values(lex_entry.pronunciations) as any; + if (!!prons && prons.length > 0) { + msg += "\nPronunciations:\n" + for (const pron of prons) { + if (!!pron.dialects) { + const dialects = Object.values(pron.dialects); + msg += ` Dialects of ${dialects.join(', ')}:\n`; + } + msg += ` ${pron.phoneticNotation}: [${pron.phoneticSpelling}]\n`; + if (pron.audioFile) { + msg += ` Audio file: ${pron.audioFile}\n`; + has_sent_audio = !has_sent_audio; + const attach = new Attachment( + pron.audioFile, + pron.audioFile.split('/').slice(-1)[0] + ); + message.channel.send('', attach); + } + } + } + } + } + console.log('Became:', msg); + return msg; } diff --git a/lib/main.ts b/lib/main.ts @@ -314,12 +314,6 @@ export class SimpOMatic { } message.answer(`Current command prefix is: \`${CONFIG.commands.prefix}\`.`); break; - } case 'ignore': { - // Man alive, someone else do this please. - break; - } case 'response': { - // TODO - break } case 'search': { const query = args.join(' ').toLowerCase(); @@ -503,9 +497,22 @@ export class SimpOMatic { process_generic(message : Message) { const { content } = message; - if (content.includes('bot')) + if (content.includes(' bot ')) message.answer("The hell you sayn' about bots?"); - // TODO: Process _rules_ appropriately. + + for (const responder of CONFIG.rules.respond) { + const match = content.match(responder.match); + const { response } = responder; + if (match && response) message.answer(response); + } + for (const rejecter of CONFIG.rules.reject) { + const match = content.match(rejecter.match); + const { response } = rejecter; + if (match) { + if (response) message.answer(response); + if (message.deletable) message.delete(); + } + } } async last_message(opts) : Promise<string> { diff --git a/lib/utils.ts b/lib/utils.ts @@ -5,101 +5,101 @@ import './extensions'; // This assumes no two string-array entries // would ever be greater than 2000 characters long. export const glue_strings = arr => { - let acc = ""; - const new_strings = []; - for (const msg of arr) - if (acc.length + msg.length >= 2000) { - new_strings.push(acc); - acc = msg; - } else { acc += msg; } - new_strings.push(acc); - return new_strings; + let acc = ""; + const new_strings = []; + for (const msg of arr) + if (acc.length + msg.length >= 2000) { + new_strings.push(acc); + acc = msg; + } else { acc += msg; } + new_strings.push(acc); + return new_strings; }; export const access = (obj: any, shiftable: string[]) => - (shiftable.length === 0) - ? obj - : access(obj[shiftable.shift()], shiftable); + (shiftable.length === 0) + ? obj + : access(obj[shiftable.shift()], shiftable); export const type: (obj: any) => string = (global => obj => - (obj === global) - ? 'global' - : ({}) - .toString.call(obj) - .match(/\s([a-z|A-Z]+)/)[1] - .toLowerCase())(this); + (obj === global) + ? 'global' + : ({}) + .toString.call(obj) + .match(/\s([a-z|A-Z]+)/)[1] + .toLowerCase())(this); export const pp = o => inspect(o, { - colors: true, - showHidden: false, - depth: 23 + colors: true, + showHidden: false, + depth: 23 }); export const deep_merge_pair = (target, source) => { - Object.keys(source).each(key => { - const target_value = target[key]; - const source_value = source[key]; + Object.keys(source).each(key => { + const target_value = target[key]; + const source_value = source[key]; - if (Array.isArray(target_value) - && Array.isArray(source_value)) { - target[key] = target_value.concat(...source_value); - } - else if (type(target_value) === 'object' - && type(source_value) === 'object') { - target[key] = deep_merge_pair(target_value, source_value); - } - else { - target[key] = source_value; - } - }); + if (Array.isArray(target_value) + && Array.isArray(source_value)) { + target[key] = target_value.concat(...source_value); + } + else if (type(target_value) === 'object' + && type(source_value) === 'object') { + target[key] = deep_merge_pair(target_value, source_value); + } + else { + target[key] = source_value; + } + }); - return target; + return target; } export const deep_merge = (...objects) => - (objects.length === 2) - ? deep_merge_pair(objects[0], objects[1]) - : deep_merge_pair(objects[0], deep_merge(objects.slice(1))); + (objects.length === 2) + ? deep_merge_pair(objects[0], objects[1]) + : deep_merge_pair(objects[0], deep_merge(objects.slice(1))); export const parse_regex = (s : string) => { - let temp = s.split('/'); - const options = temp.pop(); - temp.shift(); - const contents = temp.join('/'); - return new RegExp(contents, options); + let temp = s.split('/'); + const options = temp.pop(); + temp.shift(); + const contents = temp.join('/'); + return new RegExp(contents, options); }; export const compile_match = obj => { - const o = deep_clone(obj); - if (type(o.match) === 'string') { - o.match = parse_regex(o.match); - } - return o; + const o = deep_clone(obj); + if (type(o.match) === 'string') { + o.match = parse_regex(o.match); + } + return o; }; const recursive_regex_to_string = o => { - if (type(o) === 'regexp') { - return o.toString(); - } - if (type(o) === 'object' || type(o) === 'array') { - for (const key in o) { - o[key] = recursive_regex_to_string(o[key]); - } - return o; - } - return o; + if (type(o) === 'regexp') { + return o.toString(); + } + if (type(o) === 'object' || type(o) === 'array') { + for (const key in o) { + o[key] = recursive_regex_to_string(o[key]); + } + return o; + } + return o; } export const export_config = (obj, { ugly = false }) => { - const o = recursive_regex_to_string(deep_clone(obj)); - // Make sure all rules are unique, - // i.e. eliminate duplicate rules. - ['respond', 'reject', 'replace'] - .forEach(name => o.rules[name] = o.rules[name] - .map(JSON.stringify) - .unique() - .map(JSON.parse)); + const o = recursive_regex_to_string(deep_clone(obj)); + // Make sure all rules are unique, + // i.e. eliminate duplicate rules. + ['respond', 'reject', 'replace'] + .forEach(name => o.rules[name] = o.rules[name] + .map(JSON.stringify) + .unique() + .map(JSON.parse)); - return JSON.stringify(o, null, ugly ? null : 4); + return JSON.stringify(o, null, ugly ? null : 4); };