Simp-O-Matic

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

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:
MHELP.md | 6++++--
Mlib/default.ts | 2+-
Mlib/main.ts | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mlib/utils.ts | 5+++++
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'