commit 1b9a636f4b951f09d4b2bfd0dd3045b0f59cb5da
parent f8bf749c6bde65dc00a2a01d4bd30a649f057fc7
Author: Demonstrandum <moi@knutsen.co>
Date:   Mon, 16 Mar 2020 23:53:18 +0000
Refine help messages & added !get !set commands.
Diffstat:
4 files changed, 101 insertions(+), 14 deletions(-)
diff --git a/HELP.md b/HELP.md
@@ -1,5 +1,5 @@
 **KEY:**
-How to read this help page (the notation it uses):
+How to read the help pages (the notation it uses):
 
 `  !  ` — is the standard command prefix.
 `[...]` — specifies an option/argument to the command (required).
@@ -26,6 +26,8 @@ How to read this help page (the notation it uses):
 - `!prefix [new]` — Changes the prefix for sending this bot commands (default is `!`). Can only be one (1) character/symbol/grapheme/rune long.
 - `!ping` — Test the response-time/latency of the bot, by observing the time elapsed between the sending of this command, and the subsequent (one-word) response from the bot.
 - `!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.
 - `!alias` — Manage aliases to commands:
@@ -76,7 +78,7 @@ How to read this help page (the notation it uses):
 - `!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 BaCk_...
+- `!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.
 
 ▬▬▬
diff --git a/lib/default.ts b/lib/default.ts
@@ -10,7 +10,7 @@ export default {
 
     commands: {
         prefix: '!',
-        max_history: 100,
+        max_history: 40,
         not_understood: "Command not understood",
         aliases: {
             'img': 'image',
diff --git a/lib/main.ts b/lib/main.ts
@@ -14,7 +14,8 @@ import { execSync as shell } from 'child_process';
 
 // Local misc/utility functions.
 import './extensions';
-import { deep_merge, pp, compile_match, export_config } from './utils';
+import { deep_merge, pp, compile_match,
+         export_config, access } from './utils';
 import format_oed from './format_oed';  // O.E.D. JSON entry to markdown.
 
 // Default bot configuration JSON.
@@ -46,18 +47,21 @@ const SECRETS = JSON.parse(shell('sh ./generate_secrets.sh').toString());
 
 // Load HELP.md file, and split text smart-ly
 //  (to fit within 2000 characters).
-const HELP = read_file('./HELP.md');
-const help_sections = HELP.toString()
+const [HELP_KEY, HELP, HELP_SOURCE] = read_file('./HELP.md')
+    .toString().split('▬▬▬');
+
+const HELP_SECTIONS = HELP.toString()
     .replace(/\n  -/g, '\n      \u25b8')
     .replace(/\n- /g, '@@@\n\u2b25 ')
-    .split('@@@');
+    .split('@@@')
+    .filter(e => !!e && !!e.trim());
 
 let acc = "";
-let new_messages = [];
+let new_messages : string[] = [];
 
 // This assumes no two help-entries would ever
 //  be greater than 2000 characters long
-for (const msg of help_sections)
+for (const msg of HELP_SECTIONS)
     if (acc.length + msg.length >= 2000) {
         new_messages.push(acc);
         acc = msg;
@@ -66,6 +70,15 @@ new_messages.push(acc);
 
 const HELP_MESSAGES = new_messages;
 
+const ALL_HELP = [
+    HELP_KEY,
+    '\n▬▬▬\n', ...HELP_MESSAGES,
+    '\n▬▬▬\n', HELP_SOURCE
+];
+
+const KNOWN_COMMANDS = HELP_SECTIONS.map(e =>
+    e.slice(5).replace(/(\s.*)|(`.*)/g, ''));
+
 // Log where __dirname and cwd are for deployment.
 console.log('File/Execution locations:', {
     '__dirname': __dirname,
@@ -80,6 +93,7 @@ export class SimpOMatic {
     constructor() {
         console.log('Secrets:', pp(SECRETS));
         console.log('Configured Variables:', pp(CONFIG));
+        console.log('Known commands:', pp(KNOWN_COMMANDS));
     }
 
     static start() {
@@ -119,9 +133,40 @@ export class SimpOMatic {
                 message.answer("PONGGERS!");
                 break;
             } case 'help': {
-                message.answer('**HELP:**');
-                for (const msg of HELP_MESSAGES)
-                    message.channel.send(msg);
+                if (args.length === 0 || args[0] == 'help') {
+                    message.channel.send(HELP_SECTIONS[0]);
+                    break;
+                }
+
+                if (args[0] === 'key') {
+                    message.channel.send(HELP_KEY);
+                    break;
+                } else if (args[0] === 'source') {
+                    message.channel.send(HELP_SOURCE);
+                    break;
+                } else if (args[0] === 'all') {
+                    for (const msg of ALL_HELP)
+                        message.channel.send(msg);
+                    break;
+                }
+
+                // Assume the user is now asking for help with a command:
+                //   Sanitise:
+                let command = args[0].trim();
+                if (command.head() === CONFIG.commands.prefix)
+                    command = command.tail();
+                if (CONFIG.commands.aliases.hasOwnProperty(command))
+                    command = CONFIG.commands.aliases[command].trim().squeeze();
+                command = command.split(' ').head().trim().squeeze();
+
+                const help_index = KNOWN_COMMANDS.indexOf(command);
+
+                if (help_index === -1)
+                    message.answer(`No such command/help-page (\`${command}\`).`);
+                else
+                    message.answer(`**Help (\`${command}\`):**\n`
+                        + HELP_SECTIONS[help_index]);
+
                 break;
             } case 'id': {
                 const reply = `User ID: ${message.author.id}
@@ -130,6 +175,39 @@ export class SimpOMatic {
                 console.log(`Replied: ${reply}`);
                 message.answer(reply);
                 break;
+            } case 'get': {
+                if (args.length === 0) {
+                    message.answer('To view the entire object, use the `!export` command.');
+                    break;
+                }
+                // Accessing invalid fields will be caught.
+                try {
+                    const accessors = args[0].trim().split('.').squeeze();
+                    const resolution = access(CONFIG, accessors);
+                    message.channel.send(` ⇒ \`${resolution}\``);
+                } catch (e) {
+                    message.channel.send(`Invalid object access-path\n`
+                        + `Problem: \`\`\`\n${e}\n\`\`\``);
+                }
+                break;
+            } case 'set': {
+                if (args.length < 2) {
+                    message.answer('Please provide two arguments.\nSee `!help set`.');
+                    break;
+                }
+                try {
+                    const accessors = args[0].trim().split('.').squeeze();
+                    const parent = accessors.pop();
+                    const obj = access(CONFIG, accessors);
+                    obj[parent] = JSON.parse(args[1]);
+
+                    message.channel.send(`Assignment successful.
+                        \`${args[0].trim()} = ${obj[parent]}\``.squeeze());
+                } catch (e) {
+                    message.channel.send(`Invalid object access-path,`
+                        + `nothing set.\nProblem: \`\`\`\n${e}\n\`\`\``);
+                }
+                break;
             } case 'alias': {
                 const p = CONFIG.commands.prefix;
 
@@ -317,8 +395,10 @@ export class SimpOMatic {
                 message.answer(`A copy of this export (\`export-${today}.json\`) \
                     has been saved to the local file system.`.squeeze());
                 break;
-            }
-            default: {
+            } case '': {
+                message.answer("That's an empty command...");
+                break;
+            } default: {
                 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,11 @@ import { inspect } from 'util';
 import deep_clone from 'deepcopy';
 import './extensions';
 
+export const access = (obj: any, shiftable: string[]) =>
+    (shiftable.length === 0)
+        ? obj
+        : access(obj[shiftable.shift()], shiftable);
+
 export const type: (obj: any) => string = (global => obj =>
     (obj === global)
         ? 'global'