commit 48d106ac535492f024a044c46fcab1b806d89574
parent 1b9a636f4b951f09d4b2bfd0dd3045b0f59cb5da
Author: Demonstrandum <moi@knutsen.co>
Date: Tue, 17 Mar 2020 15:51:43 +0000
Switched to Google API, and implemeted a few more commands.
Diffstat:
7 files changed, 224 insertions(+), 71 deletions(-)
diff --git a/HELP.md b/HELP.md
@@ -1,11 +1,11 @@
-**KEY:**
-How to read the help pages (the notation it uses):
+**KEY:** How to read the help pages (the notation it uses):
` ! ` — is the standard command prefix.
`[...]` — specifies an option/argument to the command (required).
`<...>` — specifies an optional option/argument to the command (not-required).
`{a,b}` — represents a choice between a either writing `a` or `b` in its place.
` _ ` — represents the absence of a value at that particular place.
+**〈not impl.〉** — the command has not yet been implemented... please send a pull request :pleading_face:.
▬▬▬
@@ -28,15 +28,15 @@ How to read the help pages (the notation it uses):
- `!id <who>` — Print ID of user, or self if no-one is specified.
- `!get [accessor]` — Get a runtime configuration variable, using JavaScript object dot-notation.
- `!set [accessor] [json-value]` — Set a value in the runtime JavaScript configuration object.
-- `!uptime` — Display how long the bot has been running for.
-- `!clear #[number-of-messages] <@user-name>` — Clear a number of messages, from latest sent in the current channel. Will delete any recent messages, unless a specific username is provided, in which case it will only clear messages sent from that user.
+- `!uptime` **〈not impl.〉** — Display how long the bot has been running for.
+- `!clear #[number-of-messages] <@user-name>` **〈not impl.〉** — Clear a number of messages, from latest sent in the current channel. Will delete any recent messages, unless a specific username is provided, in which case it will only clear messages sent from that user.
- `!alias` — Manage aliases to commands:
- - **Have a look at the aliases list, for alternative to long commands!**
+ - **Have a look at the aliases list, for alternatives to long commands!**
- `!alias ![the-alias] ![the-command]` — to add a new alias.
- `!alias <ls>` — lists all aliases numerically.
- `!alias rm ![the-alias]` — removes the alias by name.
- - `!alias rm #[alias-index]` — removes alias by index.
-- `!ignore` — What the bot should ignore:
+ - `!alias rm #[alias-index]` — removes the alias by a numerical index.
+- `!ignore` **〈not impl.〉** — What the bot should ignore:
- `!ignore channel [#channel-name]` — ignores everything in said channel.
- `!ignore user [@user-name]` — ignores everything that user says/does.
- `!ignore user speech [@user-name]` — ignores any non-commands given by that user.
@@ -47,39 +47,40 @@ How to read the help pages (the notation it uses):
- `!ignore whitelist [type] [@name]` — Will exempt certain users or groups from any of the ignore-rules, ever. (`[type]` is either `user` or `group`)
- `!ignore <ls>` — lists all ignore rules by type.
- `!ignore rm [type] [@name]` — clears all ignore rules for a certain type (types are: `user`, `channel` or `group`).
-- `!respond` — How the bot should respond to certain messages:
+- `!respond` **〈not impl.〉** — How the bot should respond to certain messages:
- `!respond [match] [reply]` — matches an expression said (using regular-expressions, i.e. `/regex/flags`), and replies with a message.
- `!respond <ls>` — list all response rules numerically.
- `!respond rm #[rule-index]` — removes the response-rule by index.
-- `!reject` — Deletes messages meeting certain patterns:
+- `!reject` **〈not impl.〉** — Deletes messages meeting certain patterns:
- `!reject [match] <reply>` — rejects certain messages, matching a regular-expression (specifying a reply is optional).
- `!reject <ls>` — numerically lists all rejection rules.
- `!reject rm #[rule-index]` — removes the rejection-rule specified by a numerical index.
-- `!replace` — Bots currently do not have the ability to edit other users messages. We can only wait.
-- `!cron` — Run commands repeatedly based on some timer (look-up cron syntax for more info):
+- `!replace` **〈not impl.〉** — Bots currently do not have the ability to edit other users messages. We can only wait.
+- `!cron` **〈not impl.〉** — Run commands repeatedly based on some timer (look-up 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.
- `!cron <ls>` — lists all active cron-jobs numerically.
- `!cron rm #[job-index]` — removes a cron-job by index.
-- `!choose [comma-separated-values]` — Choose randomly from a list of items, separated by commas.
+- `!choose [comma-separated-values]` **〈not impl.〉** — Choose randomly from a list of items, separated by commas.
- `!define [word]` — Looks a word up in the Oxford English Dictionary.
- `!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.
-- `!gif [gif-search-terms]` — Searches for and returns a GIF matching your search.
-- `!cat` — Pussycat pictures...
-- `!news [news-search-term]` — Sends you the most relevant news on the specified topic area.
-- `!youtube [youtube-search-terms]` — Searches for and returns a relevant _YouTube_ video.
-- `!wikipedia` — Search through Wikipedia, returning the most relevant wiki-link.
-- `!translate <language> [phrase]` — Translate a phrase from a language (if none specified, it will auto-detect).
-- `!wolfram` — Query Wolfram|Alpha.
+- `!image [image-search-terms]` — Searches for images specified by the terms given, and sends a link to the most relevant one.
+- `!gif [gif-search-terms]` **〈not impl.〉** — Searches for and returns a GIF matching your search.
+- `!cat` **〈not impl.〉** — Pussycat pictures...
+- `!news [news-search-term]` **〈not impl.〉** — Sends you the most relevant news on the specified topic area.
+- `!youtube [youtube-search-terms]` **〈not impl.〉** — Searches for and returns a relevant _YouTube_ video.
+- `!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.
- `!say [phrase]` — Repeats what you told it to say.
- `!milkies` — In case you're feeling thirsty...
-- `!cowsay <options> [phrase]` — Make a cow say something, using Unix-like command line arguments.
-- `!figlet <options> [phrase]` — Print text in ASCII format, using Unix-like command line arguments.
-- `!roll <upper-bound>` — Roll a dice, default upper bound is 6.
-- `!summon [@user-name]` — Summon someone to the server by making the bot poke them in their DMs about it.
-- `!mock [phrase]` — Say something, _bUt iN a MocKiNg WaY_...
-- `!boomer [phrase]` — Say something, but in the way your demented boomer uncle would write it on Facebook.
+- `!cowsay <options> [phrase]` **〈not impl.〉** — Make a cow say something, using Unix-like command line arguments.
+- `!figlet <options> [phrase]` **〈not impl.〉** — Print text in ASCII format, using Unix-like command line arguments.
+- `!roll <upper-bound>` **〈not impl.〉** — Roll a dice, default upper bound is 6.
+- `!8ball` **〈not impl.〉** — Ask a question, receive a response.
+- `!summon [@user-name]` **〈not impl.〉** — Summon someone to the server by making the bot poke them in their DMs about it.
+- `!mock [phrase]` **〈not impl.〉** — Say something, _bUt iN a MocKiNg WaY_...
+- `!boomer [phrase]` **〈not impl.〉** — Say something, but in the way your demented boomer uncle would write it on Facebook.
▬▬▬
diff --git a/generate_secrets.sh b/generate_secrets.sh
@@ -20,6 +20,20 @@ cat <<- JSON
"oxford": {
"id": "$OXFORD_ID",
"key": "$OXFORD_KEY"
+ },
+ "google": {
+ "api_key": "$GOOGLE_API_KEY",
+ "search_id": "$GOOGLE_SEARCH_ID",
+ "type": "$GOOGLE_TYPE",
+ "project_id": "$GOOGLE_PROJECT_ID",
+ "private_key_id": "$GOOGLE_PRIVATE_KEY_ID",
+ "private_key": "$GOOGLE_PRIVATE_KEY",
+ "client_email": "$GOOGLE_CLIENT_EMAIL",
+ "client_id": "$GOOGLE_CLIENT_ID",
+ "auth_uri": "$GOOGLE_AUTH_URI",
+ "token_uri": "$GOOGLE_TOKEN_URI",
+ "auth_provider_x509_cert_url": "$GOOGLE_AUTH_PROVIDER_X509_CERT_URL",
+ "client_x509_cert_url": "$GOOGLE_CLIENT_X509_CERT_URL"
}
}
JSON
diff --git a/lib/api/google.ts b/lib/api/google.ts
@@ -0,0 +1,58 @@
+import { google } from 'googleapis';
+
+type CSE = {
+ 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."
+};
+
+// TODO: Reject results if they're from:
+// Urban Dictionary,
+// YouTube or Wikipedia.
+// 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\`\`\``));
+});
+
+export default web_search;
diff --git a/lib/default.ts b/lib/default.ts
@@ -20,6 +20,7 @@ export default {
'yt': 'youtube',
'y': 'youtube',
'd': 'define',
+ 'def': 'define',
'oed': 'define',
'oxford': 'define',
'ud': 'urban',
@@ -80,8 +81,8 @@ export default {
// For real this time:
// -> `accelarion#0764`
// a.k.a. instcel,
- // a.k.a. instgen
- // a.k.a. installgentoo
+ // a.k.a. instgen,
+ // a.k.a. installgentoo.
"409461942495871016": {
commands: true,
commands_elevated: false,
diff --git a/lib/main.ts b/lib/main.ts
@@ -15,14 +15,14 @@ import { execSync as shell } from 'child_process';
// Local misc/utility functions.
import './extensions';
import { deep_merge, pp, compile_match,
- export_config, access } from './utils';
+ export_config, access, glue_strings } from './utils';
import format_oed from './format_oed'; // O.E.D. JSON entry to markdown.
// Default bot configuration JSON.
import DEFAULT_CONFIG from './default';
// API specific modules.
-import web_search from './api/contextual';
+import web_search from './api/google';
import oed_lookup from './api/oxford';
import urban_search from './api/urban';
import { Channel } from 'discord.js';
@@ -56,29 +56,21 @@ const HELP_SECTIONS = HELP.toString()
.split('@@@')
.filter(e => !!e && !!e.trim());
-let acc = "";
-let new_messages : string[] = [];
// This assumes no two help-entries would ever
// be greater than 2000 characters long
-for (const msg of HELP_SECTIONS)
- if (acc.length + msg.length >= 2000) {
- new_messages.push(acc);
- acc = msg;
- } else { acc += msg; }
-new_messages.push(acc);
-
-const HELP_MESSAGES = new_messages;
-
-const ALL_HELP = [
+const HELP_MESSAGES = glue_strings(HELP_SECTIONS);
+const ALL_HELP = glue_strings([
HELP_KEY,
'\n▬▬▬\n', ...HELP_MESSAGES,
'\n▬▬▬\n', HELP_SOURCE
-];
+]);
const KNOWN_COMMANDS = HELP_SECTIONS.map(e =>
e.slice(5).replace(/(\s.*)|(`.*)/g, ''));
+const GIT_URL = 'https://github.com/Demonstrandum/Simp-O-Matic';
+
// Log where __dirname and cwd are for deployment.
console.log('File/Execution locations:', {
'__dirname': __dirname,
@@ -114,7 +106,7 @@ export class SimpOMatic {
const words = content.tail().split(' ');
const args = words.tail();
- let command = words[0];
+ let command = words[0].toLowerCase();
if (CONFIG.commands.aliases.hasOwnProperty(command))
command = CONFIG.commands.aliases[command].trim().squeeze();
@@ -165,13 +157,24 @@ export class SimpOMatic {
message.answer(`No such command/help-page (\`${command}\`).`);
else
message.answer(`**Help (\`${command}\`):**\n`
- + HELP_SECTIONS[help_index]);
+ + HELP_SECTIONS[help_index].trim());
break;
} case 'id': {
- const reply = `User ID: ${message.author.id}
+ if (args[0]) {
+ const matches = args[0].match(/<@(\d+)>/)
+ if (!matches) {
+ message.answer(`Please tag a user, or \
+ provide no argument(s) at all. See \`!help id\``
+ .squeeze());
+ } else {
+ message.answer(`User ID: \`${matches[1]}\``);
+ }
+ break;
+ }
+ const reply = `User ID: \`${message.author.id}\`
Author: ${message.author}
- Message ID: ${message.id}`.squeeze();
+ Message ID: \`${message.id}\``.squeeze();
console.log(`Replied: ${reply}`);
message.answer(reply);
break;
@@ -217,6 +220,41 @@ export class SimpOMatic {
message.answer('List of **Aliases**:\n')
message.channel.send('**KEY: `Alias` ↦ `Command it maps to`**\n\n'
+ lines.join('\n'));
+ break;
+ }
+
+ // Parse `!alias rm` command.
+ if (args[0] === 'rm' && args.length > 1) {
+ const aliases = CONFIG.commands.aliases;
+ const keys = Object.keys(aliases);
+ let match, index, alias;
+ if (match = args[1].match(/^#?(\d+)/)) {
+ index = Number(match[1]) - 1;
+ if (index >= keys.length) {
+ message.answer('No alias exists at such an index'
+ + ` (there are only ${keys.length} indices).`);
+ break;
+ }
+ alias = keys[index];
+ keys.each((_, i) => i === index
+ ? delete aliases[alias]
+ : null);
+ } else {
+ alias = args[1];
+ if (alias[0] === '!') alias = alias.tail();
+ index = keys.indexOf(alias);
+ if (index === -1) {
+ message.answer(`There does not exist any alias \
+ with the name \`${p}${alias}\`.`.squeeze());
+ break;
+ }
+ keys.each((a, _) => a === alias
+ ? delete alias[alias]
+ : null);
+ }
+ message.answer(`Alias \`${p}${alias}\` at index \
+ number ${index + 1}, has been deleted.`.squeeze());
+ break;
}
// Check last:
@@ -234,6 +272,20 @@ export class SimpOMatic {
message.channel.send(
'**Alias added:**\n >>> ' +
`\`${p}${args[0]}\` now maps to \`${p}${args.tail().join(' ')}\``);
+ } else {
+ if (args.length === 1) {
+ if (args[0] in CONFIG.commands.aliases) {
+ const aliases = Object.keys(CONFIG.commands.aliases);
+ const n = aliases.indexOf(args[0]) + 1;
+ message.answer(`${n}. \`${p}${args[0]}\` ↦ \`${p}${CONFIG.commands.aliases[args[0]]}\``);
+ break;
+ } else {
+ message.answer('No such alias found.');
+ break;
+ }
+ }
+ message.answer('Invalid number of arguments to alias,\n'
+ + 'Please see `!help alias`.');
}
break;
} case 'prefix': {
@@ -250,38 +302,33 @@ 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': {
+
+ break
} case 'search': {
- const query = args.join(' ');
+ const query = args.join(' ').toLowerCase();
web_search({
- type: 'web',
+ kind: 'web',
query,
- key: SECRETS.rapid.key
- }).then((res: object) => {
- if (res['value'].length === 0) {
- message.answer('No such results found.');
- return;
- }
- message.answer(`Web search for ‘${query}’, \
- found: ${res['value'][0].url}`.squeeze());
- }).catch(e => message.answer(`Error fetching results:\n${e}`));
+ key: SECRETS.google.api_key,
+ id: SECRETS.google.search_id
+ }).then((res) => message.answer(res))
+ .catch(e => message.answer(e));
break;
} case 'image': {
- const query = args.join(' ');
+ const query = args.join(' ').toLowerCase();
web_search({
- type: 'image',
+ kind: 'image',
query,
- key: SECRETS.rapid.key
- }).then(res => {
- if (res['value'].length === 0) {
- message.answer('No such images found.');
- return;
- }
- message.answer(`Image found for ‘${query}’: \
- ${res['value'][0].url}`.squeeze());
- }).catch(e =>
- message.answer(`Error fetching image:\n${e}`));
+ key: SECRETS.google.api_key,
+ id: SECRETS.google.search_id
+ }).then((res) => message.answer(res))
+ .catch(e => message.answer(e));
break;
} case 'define': {
message.answer('Looking in the Oxford English Dictionary...');
@@ -366,7 +413,7 @@ export class SimpOMatic {
} case 'ily': {
message.answer('Y-you too...');
break;
- }case 'say': {2
+ } case 'say': {
message.answer(`Me-sa says: “${args.join(' ')}”`);
break;
} case 'export': {
@@ -395,10 +442,27 @@ export class SimpOMatic {
message.answer(`A copy of this export (\`export-${today}.json\`) \
has been saved to the local file system.`.squeeze());
break;
+ } case 'github': {
+ message.answer(`${GIT_URL}/`);
+ break;
+ } case 'fork': {
+ message.answer(`${GIT_URL}/fork`)
+ break;
+ } case 'issue': {
+ message.answer(`${GIT_URL}/issues`)
+ break;
} case '': {
message.answer("That's an empty command...");
break;
} default: {
+ if (KNOWN_COMMANDS.includes(command)) {
+ const p = CONFIG.commands.prefix;
+ message.reply(`:scream: *gasp!* — The \`${p}${command}\` \
+ command has not been implemented yet. \
+ Quick send a pull request! Just type in \
+ \`${p}fork\`, and get started...`.squeeze());
+ break;
+ }
message.answer(`
:warning: ${CONFIG.commands.not_understood}.
> \`${CONFIG.commands.prefix}${command}\``.squeeze());
diff --git a/lib/utils.ts b/lib/utils.ts
@@ -2,6 +2,20 @@ import { inspect } from 'util';
import deep_clone from 'deepcopy';
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;
+};
+
export const access = (obj: any, shiftable: string[]) =>
(shiftable.length === 0)
? obj
diff --git a/package.json b/package.json
@@ -36,6 +36,7 @@
"@types/ws": "^7.2.2",
"deepcopy": "^2.0.0",
"discord.js": "11.6.1",
+ "googleapis": "^48.0.0",
"tslib": "^1.11.1",
"typescript": "^3.8.3",
"unirest": "^0.6.0"