From 09dae249ef687821fbe4bb58c46b6fba21ada4d1 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 8 Aug 2019 22:09:03 -0700 Subject: [PATCH] initial push --- .editorconfig | 12 + .eslintrc.json | 3 + .gitattributes | 22 + .gitignore | 52 ++ .vscode/settings.json | 3 + bundles/chatbot/daemons/chatbot.js | 755 ++++++++++++++++++++++ bundles/chatbot/helpers/chatbot.js | 80 +++ bundles/chatbot/includes/chatdefaults.js | 4 + bundles/chatbot/peg/messageparts.pegjs | 52 ++ edenconfig.js | 3 + package-lock.json | 781 +++++++++++++++++++++++ package.json | 20 + 12 files changed, 1787 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 bundles/chatbot/daemons/chatbot.js create mode 100644 bundles/chatbot/helpers/chatbot.js create mode 100644 bundles/chatbot/includes/chatdefaults.js create mode 100644 bundles/chatbot/peg/messageparts.pegjs create mode 100644 edenconfig.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5cd293 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{bat,cmd}] +end_of_line = crlf diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a770ead --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends" : "@edenjs/eden" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c54ea1d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto + +# For the following file types, normalize line endings to LF on +# checkin and prevent conversion to CRLF when they are checked out +# (this is required in order to prevent newline related issues like, +# for example, after the build script is run) +.* text eol=lf +*.css text eol=lf +*.html text eol=lf +*.jade text eol=lf +*.js text eol=lf +*.json text eol=lf +*.less text eol=lf +*.scss text eol=lf +*.md text eol=lf +*.sh text eol=lf +*.txt text eol=lf +*.xml text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25d22d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Debug log from npm +npm-debug.log + +# Error log from yarn +yarn-error.log + +# yarn lock - do work pls +yarn.lock + +# nodejs modules +node_modules/ + +# edenjs stuff +data/ +config.js +edenappconfig.js + +# IDE +.idea +.remote-sync.json + +# Log and Sentry +*.log +*.sentry + +# Redis +*.rdb + +# sqlite files +*.db +*.sdb +*.sqlite +*.db3 +*.s3db +*.sqlite3 +*.sl3 +*.db2 +*.s2db +*.sqlite2 +*.sl2 + +# Other junk +.*.swp +._* +.DS_Store +.hg +.npmrc +.lock-wscript +.svn +.wafpickle-* +config.gypi +CVS diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4718ffd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "eslint.enable": true +} \ No newline at end of file diff --git a/bundles/chatbot/daemons/chatbot.js b/bundles/chatbot/daemons/chatbot.js new file mode 100644 index 0000000..944a0ba --- /dev/null +++ b/bundles/chatbot/daemons/chatbot.js @@ -0,0 +1,755 @@ +const Daemon = require('daemon'); +const config = require('config'); +const messageArgsParser = require('parsers/chatbot/messageparts'); +const { EventEmitter } = require('events'); +const equal = require('deep-equal'); + +const User = model('user'); +const Chat = model('chat'); + +const chatHelper = helper('chat'); + +const setChatDefaults = require('../includes/chatdefaults.js'); + +const EMOJI_LIST = ['🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮', '🇯', '🇰', '🇱', '🇲', '🇳', '🇴', '🇵', '🇶', '🇷', '🇸', '🇹', '🇺', '🇻', '🇼', '🇽', '🇾', '🇿']; +const CANCEL_EMOJI = '🚫'; + +async function carelessCall(fn) { + try { await fn(); } catch (_) { /* whocare */ } +} + +/** + * Create Chat Bot Daemon Class + * + * @cluster bot + */ +class ChatBot extends Daemon { + /** + * Construct Chat Bot Daemon Class + */ + constructor() { + // Run super + super(); + + this._commandRegistry = []; + this._argRegistry = new Map(); + this._responseRegistry = new Map(); + + this._events = new EventEmitter(); + this._events.on('error', err => global.printError(err)); + + setChatDefaults(this); + + // Run build + this.building = this.build(); + + this.eden.endpoint('chatbot.setResponse', async (...args) => { + return await this.setResponse(...args); + }, false); + + this.eden.endpoint('chatbot.setArg', async (...args) => { + return await this.setArg(...args); + }, false); + + this.eden.endpoint('chatbot.addCommand', async (...args) => { + return await this.addCommand(...args); + }, false); + + this.eden.endpoint('chatbot.setStatus', async (...args) => { + return await this.setStatus(...args); + }, false); + + this.eden.endpoint('chatbot.respond', async (...args) => { + return await this.respond(...args); + }, false); + + this.eden.endpoint('chatbot.isReady', () => { + return true; + }, false); + + this.eden.emit('chatbot.ready', false); + } + + async setStatus(name) { + // i feel dirty + chatHelper.member.set(this._botUser, { get() { return ''; }, set() {} }, 'playing', name); + } + + async _getResponse(responseName) { + let registeredResponse = this._responseRegistry.get(responseName); + + if (!registeredResponse) { + registeredResponse = await new Promise(resolve => this._events.on(`addedResponse.${responseName}`, resolve)); + } + + return registeredResponse; + } + + async setResponse(name, fn) { + const response = async (...responseArgs) => { + const getResponse = async a => this._getResponse(a); + return await fn(getResponse, ...responseArgs); + }; + + this._responseRegistry.set(name, response); + this._events.emit(`addedResponse.${name}`, response); + } + + async setArg(typeName, opts) { + const arg = { + query : opts.query || (name => `Please enter \`${name}\``), + + handle : opts.handle ? opts.handle : v => v, + + getOptions : opts.getOptions || null, + strict : opts.strict || false, + strictCheck : opts.strictCheck || false, + + schemas : opts.schemas || [], + }; + + this._argRegistry.set(typeName, arg); + this._events.emit(`addedArg.${typeName}`, arg); + } + + async addCommand(rawSchemas, fn = null, desc = null, emoji = null, ver = 1) { + // If rawSchemas is singular wrap in an array + if (!(rawSchemas instanceof Array)) rawSchemas = [rawSchemas]; + + // Be able to handle addCommand(schemas, desc, emoji?, ver?) + if (typeof fn === 'string') { + ver = emoji; + emoji = desc; + desc = fn; + fn = null; + } + + const schemas = rawSchemas.map((rawSchema) => { + let schema = []; + + if (typeof rawSchema === 'string') { + for (const schemaPart of rawSchema.split(' ')) { + if (schemaPart[0] === '[' && schemaPart[schemaPart.length - 1] === ']') { + const [partName, argType] = schemaPart.slice(1, -1).split(':'); + schema.push({ name : partName, argType }); + } else { + schema.push({ type : 'text', text : schemaPart }); + } + } + } else if (rawSchema instanceof Array) { + schema = rawSchema; + } + + return schema; + }); + + // Wait for all args to be defined + for (const schema of schemas) { + for (const schemaPart of schema) { + if (schemaPart.name === undefined || schemaPart.name === null) continue; + + if (!this._argRegistry.get(schemaPart.argType)) { + await new Promise(resolve => this._events.on(`addedArg.${schemaPart.argType}`, resolve)); + } + } + } + + this._commandRegistry.push({ + schemas, fn, desc, emoji, ver, + }); + } + + async build() { + this.eden.on('eden.chat.message', async (messageData) => { + try { await this._onRawMesage(messageData); } catch (err) { + global.printError(err); + } + }, true); + + this._botUser = await User.findById(config.get('chatbot.userId')); + } + + async respond(chat, responseName, ...responseArgs) { + await this._respond(chat, responseName, ...responseArgs); + } + + async dm(user, responseName, ...responseArgs) { + const chat = await chatHelper.create(user, [user, this._botUser], { type : 'dm' }); + await this._respond(chat, responseName, ...responseArgs); + } + + async _respond(chat, responseName, ...responseArgs) { + const registeredResponse = await this._getResponse(responseName); + const response = await registeredResponse(...responseArgs); + + return await chatHelper.message.send(this._botUser, chat, { + message : response.message || '', + react : (response.reactions || []).reduce((a, c) => { a[c] = { [this._botUser.get('_id')] : new Date() }; return a; }, {}), + embeds : [{ + primary : true, + buttons : response.buttons || [], + fields : response.fields || [], + image : response.image || null, + color : response.color || [], + thumbnail : response.thumbnail || null, + url : response.url || null, + title : response.title || null, + }], + }); + } + + async _callInputArgHandles(message, inputArg, argMap) { + const argDef = this._argRegistry.get(inputArg.commandArg.argType); + + const matchingSchema = argDef.schemas.find(s => equal(this._argToSchema(inputArg.arg), s)); + if (!matchingSchema) return [null, await this.respond(message.chat, 'badType')]; + + if (argDef.handle !== null) { + try { + const handleRes = await argDef.handle(inputArg.arg, message.from, argMap); + if (handleRes !== null && handleRes !== undefined) inputArg.arg = handleRes; + } catch (err) { + return [null, await this.respond(message.chat, 'handleError', err)]; + } + } + + if (argDef.strictCheck) { + const options = await argDef.getOptions(message.from, argMap); + if (!options.find(option => equal(option.val, inputArg.arg))) { + return [null, await this.respond(message.chat, 'badType')]; + } + } + + return [inputArg, null]; + } + + async _query(message, props) { + // Automatically enable strict if strict checking enabled + if (props.strictCheck) props.strict = true; + + // If strict, handle having 1 or 0 options automatically + if (props.strict && props.options !== null) { + if (props.options.length === 1) { + return [props.options[0].val, true, null]; // lol + } if (props.options.length === 0) { + return [null, null, await this._respond(message.chat, 'noValidArg')]; + } + } + + await this.eden.set(`chatbot.awaitingreply.${message.from.get('_id')}`, this.eden.cluster + this.eden.id, 1000 * 40); + + const safeOptions = props.options ? props.options.slice(0, 19) : []; + + const buttons = safeOptions.map((option, i) => { + return { + emoji : option.emoji || EMOJI_LIST[i], + label : option.label, + id : i, + }; + }); + + const rawQueryMessage = await this._respond(message.chat, 'query', { + message : props.message, + fields : safeOptions.length >= 1 ? [{ + name : 'Options:', + text : safeOptions.map((option, i) => { + return `${option.emoji || EMOJI_LIST[i]} - **${option.label}** - ${option.desc}`; + }).join('\n'), + }] : [], + buttons : [{ emoji : CANCEL_EMOJI, label : 'cancel', id : 'cancel' }, ...buttons], + }); + + let [replyArg, replyButtonID, foundCommandData] = [null, null]; + + // Create promise and hoist the resolve and reject methods + let responsePromiseResolve = null; + let responsePromiseReject = null; + const responsePromise = new Promise((resolve, reject) => { + responsePromiseResolve = resolve; + responsePromiseReject = reject; + }); + + // Create timeout for this query + setTimeout(() => { + responsePromiseReject(new Error('timeout')); + }, 1000 * 35); + + // Create listener for button presses + const buttonListener = ({ button, member }) => { + if (member !== message.from.get('_id')) return; + responsePromiseResolve([null, button.id, null]); + }; + + // Create listener for replies + const replyListener = (recievedReplyArgs, recievedReplyText) => { + if (recievedReplyArgs.length === 0) { + responsePromiseResolve([null, null, null]); + return; + } + + const [registeredCommand, inputArgMap] = this._findRegisteredCommand(recievedReplyArgs); + + if (recievedReplyArgs.length > 1 && registeredCommand !== null) { + responsePromiseResolve([null, null, [recievedReplyArgs, registeredCommand, inputArgMap]]); + } else if (props.allowText && recievedReplyArgs.length > 1 && recievedReplyArgs[0].type === 'text' && registeredCommand === null) { + responsePromiseResolve([{ type : 'text', text : recievedReplyText }, null, null]); + } else { + responsePromiseResolve([recievedReplyArgs[0], null, null]); + } + }; + + // Register the listeners to events + this._events.once(`reply.${message.from.get('_id')}`, replyListener); + this.eden.on(`eden.chat.message.buttonPress.${rawQueryMessage.get('_id')}`, buttonListener, true); + + // Macro to remove listeners and the query message + const done = async () => { + try { + await this.eden.del(`chatbot.awaitingreply.${message.from.get('_id')}`); + carelessCall(async () => await chatHelper.message.remove(rawQueryMessage)); + } catch (err) { + global.printError(err); + } + + this._events.off(`reply.${message.from.get('_id')}`, replyListener); + this.eden.off(`eden.chat.message.buttonPress.${rawQueryMessage.get('_id')}`, buttonListener); + }; + + try { + [replyArg, replyButtonID, foundCommandData] = await responsePromise; + } catch (err) { + await done(); + + if (err.message === 'timeout') { + return [null, null, await this._respond(message.chat, 'queryTimeout')]; + } + + throw err; + } + + await done(); + + if (replyArg !== null) { + if (replyArg.type === 'text' && replyArg.text === 'cancel') { + return [null, null, await this._respond(message.chat, 'argCancelled')]; + } + + if (safeOptions.find(o => equal(o.val, this._argToGeneric(replyArg)))) { + return [replyArg, true, null]; + } + + return [replyArg, false]; + } if (replyButtonID !== null) { + if (replyButtonID === 'cancel') { + return [null, null, await this._respond(message.chat, 'argCancelled')]; + } + + return [safeOptions[replyButtonID].val, true, null]; + } + + if (foundCommandData !== null) { + const [args, registeredCommand, inputArgMap] = foundCommandData; + this._handleCommandData(message, args, registeredCommand, inputArgMap, null); + return [null, null, null]; + } + + return [null, null, await this._respond(message.chat, 'argMissing')]; + } + + async _queryForInputArg(message, missingArg, argMap, oldResponses = [], iters = 0) { + const argDef = this._argRegistry.get(missingArg.argType); + + const options = argDef.getOptions ? await argDef.getOptions(message.from, argMap) : null; + + const [res, exact, queryResponse] = await this._query(message, { + message : await argDef.query(missingArg.name), + allowText : argDef.schemas.find(schema => schema.type === 'text'), + strict : argDef.strict, + options, + }); + + oldResponses.forEach(async (response) => { + if (response) carelessCall(async () => await chatHelper.message.remove(response)); + }); + + if (res !== null) { + const fullArg = { + arg : res, + commandArg : missingArg, + }; + + if (exact) return fullArg; + + const [handledReply, handleResponse] = await this._callInputArgHandles(message, fullArg); + + if (handledReply === null) { + const responses = [queryResponse, handleResponse]; + return await this._queryForInputArg(message, missingArg, argMap, responses, iters + 1); + } + + return handledReply; + } + + return null; + } + + async _queryForMatchArg(message, options, oldResponses = [], iters = 0) { + const [res, exact, queryResponse] = await this._query(message, { + message : 'Choose a command', + strict : true, + options, + }); + + oldResponses.forEach(async (response) => { + if (response) carelessCall(async () => await chatHelper.message.remove(response)); + }); + + if (res === null) return null; + + if (!exact) { + const response = await this._respond(message.chat, 'cnf'); + return await this._queryForMatchArg(message, options, [response, queryResponse], iters + 1); + } + + return res; + } + + async _handleArgs(message, args) { + const findRes = this._findRegisteredCommand(args); + return await this._handleCommandData(message, args, ...findRes); + } + + async _processInputArg(message, map, argName, arg) { + // Skip if entire input arg is null, not just the arg part, this is intentional + if (arg === null) { + map[argName] = null; + } else if (arg.arg === null) { + // Query to get the arg + const queryRes = await this._queryForInputArg(message, arg.commandArg, map); + // Return if the query was cancelled + if (queryRes === null) throw new Error('cancelled'); + // Set arg in map to result + map[argName] = queryRes.arg; + } else { + // Handle the provided arg + const [handledInputArg, handleErrorResponse] = await this._callInputArgHandles(message, arg, map); + + // If the arg was handled successfully + if (handledInputArg !== null) { + map[argName] = handledInputArg.arg; + } else { + // Query for a new arg instead + const queryRes = await this._queryForInputArg(message, arg.commandArg, map); + // Remove error message from handling + carelessCall(async () => await chatHelper.message.remove(handleErrorResponse)); + // Return if the query was cancelled + if (queryRes === null) throw new Error('cancelled'); + // Set arg in map to result + map[argName] = queryRes.arg; + } + } + } + + async _handleCommandData(message, args, registeredCommand, inputArgMap, furtherMatches) { + if (furtherMatches) { + const options = furtherMatches.map(([furtherMatchCommand, furtherMatchArg]) => { + return { + label : furtherMatchArg.type === 'text' ? furtherMatchArg.text : furtherMatchArg.title, + val : furtherMatchArg, + desc : furtherMatchCommand.desc, + emoji : furtherMatchCommand.emoji, + }; + }); + + const furtherRes = await this._queryForMatchArg(message, options); + if (furtherRes === null) throw new Error('dropped'); + + return await this._handleArgs(message, [...args, furtherRes]); + } + + if (registeredCommand !== null && inputArgMap !== null) { + const processedInputArgMap = {}; + + for (const [inputArgName, inputArg] of Object.entries(inputArgMap)) { + try { + await this._processInputArg(message, processedInputArgMap, inputArgName, inputArg); + } catch (err) { + if (err.message === 'cancelled') throw new Error('dropped'); + throw err; + } + } + + // Create easy public response macro + const respondMacro = async (responseName, ...responseArgs) => { + await this.respond(message.chat, responseName, ...responseArgs); + }; + + // Create easy DM macro + const dmMacro = async (responseName, ...responseArgs) => { + await this.dm(message.from, responseName, ...responseArgs); + if (message.chat.get('type') !== 'dm') await respondMacro('dmNote'); + }; + + // Create easy call macro + const callMacro = async (...callArgs) => { + return await this._handleArgs(message, callArgs); + }; + + // Create easy call macro + const queryMacro = async (...queryArgs) => { + return await this._query(message, ...queryArgs); + }; + + // Support legacy commands (oof) + if (registeredCommand.ver === 1) { + // Call the registered function + return await registeredCommand.fn(respondMacro, + new Proxy(processedInputArgMap, { get(k, v) { return (k[v] || null); } }), + message.from, + callMacro, + queryMacro); + } + + return await registeredCommand.fn({ + public : respondMacro, + dm : dmMacro, + respond : respondMacro, + args : new Proxy(processedInputArgMap, { get(k, v) { return (k[v] || null); } }), + from : message.from, + call : callMacro, + query : queryMacro, + isDM : message.chat.get('type') === 'dm', + }); + } + + // Respond with command not found + await this.respond(message.chat, 'cnf'); + throw new Error('dropped'); + } + + _commandMatchArgToGeneric({ + name, type, id, tag, text, + }) { + // Return if this is an input arg, not a match arg + if (name !== null && name !== undefined) return null; + if (type === 'tag') return { type, id, tag }; + if (type === 'text') return { type, text }; + return {}; // UNIMPLEMENTED + } + + // Create generic form of arg, will equal to match args + _argToGeneric({ + type, id, tag, text, + }) { + if (type === 'tag') return { type, id, tag }; + if (type === 'text') return { type, text }; + return {}; // UNIMPLEMENTED + } + + // Create schema form of arg, will equal to input args/schemas + _argToSchema({ type, tag }) { + if (type === 'tag') return { type, tag }; + if (type === 'text') return { type }; + return {}; // UNIMPLEMENTED + } + + _findLongerRegisteredCommand(args) { + const matching = []; + + for (const registeredCommand of this._commandRegistry) { + // Skip unlisted commands + if (registeredCommand.desc === null) continue; + + for (const schema of registeredCommand.schemas) { + // Get position of the first input arg, defaulting to the end if not existing + let firstCommandInputArgPosition = schema + .findIndex(commandArg => commandArg.name !== null && commandArg.name !== undefined); + if (firstCommandInputArgPosition === -1) firstCommandInputArgPosition = schema.length; + + // Check if there are match args existing after the first input arg + const matchAfterInput = !!schema + .slice(firstCommandInputArgPosition + 1) + .find(commandArg => commandArg.name === null || commandArg.name === undefined); + + // Skip if match after inputs found + if (matchAfterInput) continue; + + // Shorten the command arguments to the first input arg + const shorterCommandMatchArgs = schema + .slice(0, firstCommandInputArgPosition - 1) + .map(commandArg => this._commandMatchArgToGeneric(commandArg)); + + // Convert args to generic so they can be used to match + const potentialMatchArgs = args + .map(arg => this._argToGeneric(arg)); + + // Check if our potential args match the shortened command args + if (equal(shorterCommandMatchArgs, potentialMatchArgs)) { + // Get the part of the command cut off to produce the match + const trailingCommandMatchArg = schema[firstCommandInputArgPosition - 1]; + + // Add to list of matching + matching.push([registeredCommand, trailingCommandMatchArg]); + + // Skip all other schemas for this command + break; + } + } + } + + return matching; + } + + _findRegisteredCommand(args) { + for (const registeredCommand of this._commandRegistry) { + // Skip if not a real command + if (registeredCommand.fn === null) continue; + + for (const schema of registeredCommand.schemas) { + // List of match args for command (remove input args/nulls) + const commandMatchArgs = schema + .map(commandArg => this._commandMatchArgToGeneric(commandArg)); + + // Get provided args in positions the command's match args are in + const potentialMatchArgs = schema + .map((commandArg, i) => { + if (commandArg.name !== null && commandArg.name !== undefined) return null; + return args[i] ? this._argToGeneric(args[i]) : null; + }); + + // Check if our match args match up to the command's match args + if (equal(commandMatchArgs, potentialMatchArgs)) { + // Iterate the required args and make a formatted input arg from the input + const inputArgMap = schema + .map((commandArg, i) => { + if (commandArg.name === null || commandArg.name === undefined) return null; + + // Check if there are multiple trailing args, and this is the last text arg + if (args.length > (i + 2) && args[i] && args[i].type === 'text' && (i + 1) === schema.length) { + // Check registered info for command to check if last arg can be command + const canBeText = !!this._argRegistry.get(commandArg.argType).schemas + .find(argSchema => argSchema.type === 'text'); + + // Check that all following tags are also text + const restAreText = !args + .slice(i + 1) + .find(arg => arg.type !== 'text'); + + // Set the arg to be all remaining args if can be text and rest are text + if (canBeText && restAreText) { + args[i].text = args.slice(i).map(arg => arg.raw + arg.rawSep).join(''); + } + } + + return { commandArg, arg : args[i] || null }; + }) + .filter(arg => arg !== null) + .reduce((map, arg) => { + map[arg.commandArg.name] = arg; + return map; + }, {}); + + // Return the registered command, and the map of input args + return [registeredCommand, inputArgMap]; + } + } + } + + // Return with longer matching commands if found + const matching = this._findLongerRegisteredCommand(args); + if (matching.length > 0) return [null, null, matching]; + + return [null, null, null]; + } + + // go get em jimmy + async _fightForMessage(rawMessage) { + const myId = this.eden.cluster + this.eden.id; + + const awaitingReplyData = await this.eden.get(`chatbot.awaitingreply.${rawMessage.from}`); + + if (awaitingReplyData && awaitingReplyData === myId) { + // This should be a reply, and its for us + return 2; + } if (awaitingReplyData && awaitingReplyData !== myId) { + // This should be a reply, and its not for us + return 0; + } + + const unlockMessage = await this.eden.lock(`chatbot.message.${rawMessage.id}`); + + const dist = await this.eden.get('chatbot.distribution.*') || {}; + + // if (!Object.values(dist).find(chats => chats.length < dist[myId].length)) { + if (!Object.values(dist).find(count => count < dist[myId])) { + // await this.eden.set(`chat.distribution.${myId}`, [...dist[myId], rawMessage.get('_id')]); + // await this.eden.incr(`chat.distribution.${myId}`); + await this.eden.set(`chatbot.distribution.${myId}`, (dist[myId] || 0) + 1); + unlockMessage(); + return 1; + } + + unlockMessage(); + return 0; + } + + async _onRawMesage(rawMessage) { + await this.building; + + // Return if its empty + if (rawMessage.raw.length === 0) return; + + // Return if its from us + if (rawMessage.from === this._botUser.get('_id')) return; + + // Fight over the message + const messageFightRes = await this._fightForMessage(rawMessage); + + // Return if we did not win the fight + if (messageFightRes === 0) return; + + // TODO Add automatic invalidation + const basicChatInfo = await this.eden.get(`chatbot.basicchatinfo.${rawMessage.chat}`, async () => { + const chat = await Chat.findById(rawMessage.chat); + return { + isInChat : !!chat.get().members.find(c => c.id === this._botUser.get('_id')), + isDM : chat.get('type') === 'private', + }; + }); + + // if we're not in the chat somehow, ignore + if (!basicChatInfo.isInChat) return; + + // Use pegjs parser to parse message arguments + const { args, leadingSep } = messageArgsParser.parse(rawMessage.raw); + + // Detect if message is a command + const isCommand = leadingSep.length <= 0 && config.get('chatbot.commandArgs').find(cmdArg => equal(cmdArg, this._argToGeneric(args[0]))); + + // If its not a DM or a command its not worth our time, return + if (!isCommand && !basicChatInfo.isDM) return; + + // trim the first arg if this is a command (ex: removing .gm8) + if (isCommand) args.shift(); + + // Check if its a reply to us, or just a normal command + if (messageFightRes === 2) { + // Rebuild text from the remaining args + const text = leadingSep + args.map(arg => arg.raw + arg.rawSep).join(''); + + this._events.emit(`reply.${rawMessage.from}`, args, text); + } else { + try { + await this._handleArgs({ + chat : await Chat.findById(rawMessage.chat), + from : await User.findById(rawMessage.from), + }, args); + } catch (err) { + if (err.message === 'dropped') return; + throw err; + } + } + } +} + +module.exports = ChatBot; diff --git a/bundles/chatbot/helpers/chatbot.js b/bundles/chatbot/helpers/chatbot.js new file mode 100644 index 0000000..fcf9ccc --- /dev/null +++ b/bundles/chatbot/helpers/chatbot.js @@ -0,0 +1,80 @@ +const Helper = require('helper'); + +/** + * extend chat bot helper + * + * @extends {helper} + */ +class ChatBotHelper extends Helper { + async waitReady() { + return await new Promise((resolve) => { + const done = () => { + resolve(); + this.eden.off('chatbot.ready', done); + }; + + this.eden.once('chatbot.ready', done); + this.eden.call('chatbot.isReady', false).then(done); + }); + } + + async setResponse(name, fn) { + return await this.eden.call('chatbot.setResponse', name, fn, false); + } + + async setArg(typeName, opts) { + return await this.eden.call('chatbot.setArg', typeName, opts, false); + } + + async addCommand(schemas, fn = null, description = null, emoji = null, ver = 1) { + return await this.eden.call('chatbot.addCommand', schemas, fn, description, emoji, ver, false); + } + + async setStatus(name) { + return await this.eden.call('chatbot.setStatus', name, false); + } + + async respond(chat, responseName, ...responseArgs) { + return await this.eden.call('chatbot.respond', chat, responseName, ...responseArgs, false); + } + + async loadTagArg(tagArg) { + const m = await model(tagArg.tag).findById(tagArg.id); + + return { + title : tagArg.title, + model : m, + }; + } + + unloadTagArg(loadedTag) { + return { + title : loadedTag.title, + tag : loadedTag.model.constructor.name.toLowerCase(), + id : loadedTag.model.get('_id').toString(), + }; + } + + tagArgToText(tagArg) { + return `<${tagArg.tag}:${tagArg.title}|${tagArg.id}>`; + } + + argToText(arg) { + if (arg.type === 'text') { + return arg.text; + } + + return this.tagArgToText(arg); + } + + loadedTagArgToText(loadedTag) { + return this.tagArgToText(this.unloadTagArg(loadedTag)); + } +} + +/** + * export built chat bot helper + * + * @type {ChatBotHelper} + */ +module.exports = new ChatBotHelper(); diff --git a/bundles/chatbot/includes/chatdefaults.js b/bundles/chatbot/includes/chatdefaults.js new file mode 100644 index 0000000..b9ae025 --- /dev/null +++ b/bundles/chatbot/includes/chatdefaults.js @@ -0,0 +1,4 @@ +module.exports = function setChatDefaults() { + // This is just a placeholder, so you can overwrite it + +}; diff --git a/bundles/chatbot/peg/messageparts.pegjs b/bundles/chatbot/peg/messageparts.pegjs new file mode 100644 index 0000000..38dfbe7 --- /dev/null +++ b/bundles/chatbot/peg/messageparts.pegjs @@ -0,0 +1,52 @@ +Value + = leadingSep:[ \t\n\r]* args:( + arg:Arg sep:[ \t\n\r]* + { return [arg, sep.join('')]; } + )* lastArg:Arg? + { + return { + args: (lastArg !== null ? [...args, [lastArg, '']] : args).map(([arg, sep]) => { + return Object.assign({}, arg, { + rawSep: sep, + }); + }), + leadingSep: leadingSep.join(''), + } + } + +Arg + = '<' tagChars:TagNameCharacter+ ':' titleChars:TagTitleCharacter+ '|' idChars:TagIdCharacter+ '>' { + return { + type: 'tag', + id: idChars.join(''), + tag: tagChars.join(''), + title: titleChars.join(''), + raw: text(), + }; + } + / textArg:TextArg { return { type: 'text', text: textArg, raw: text(), }; } + +TagNameCharacter + = !([ \t\n\r] / ':' / '\\') char:. { return char; } + / "\\" sequence:(':' / "\\") { return sequence; } + +TagTitleCharacter + = !([\t\n\r] / '|' / '\\') char:. { return char; } + / "\\" sequence:('|' / "\\") { return sequence; } + +TagIdCharacter + = !([ \t\n\r] / '>' / '\\') char:. { return char; } + / "\\" sequence:('"' / ">") { return sequence; } + +TextArg + = '"' chars:DoubleStringCharacter+ '"' !([^ \t\n\r]) { return chars.join(''); } + / chars:TextCharacter+ { return chars.join(''); } + +DoubleStringCharacter + = !('"' / "\\") char:. { return char; } + / "\\" sequence:('"' / "\\") { return sequence; } + +TextCharacter + = !([ \t\n\r] / "\\") char:. { return char; } + / "\\" char:. { return char; } + / "\\" { return "\\"; } \ No newline at end of file diff --git a/edenconfig.js b/edenconfig.js new file mode 100644 index 0000000..71f11a9 --- /dev/null +++ b/edenconfig.js @@ -0,0 +1,3 @@ +const config = {}; + +module.exports = config; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d65676d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,781 @@ +{ + "name": "@edenup/chatbot", + "version": "1.0.8", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@edenjs/eslint-config-eden": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@edenjs/eslint-config-eden/-/eslint-config-eden-2.0.14.tgz", + "integrity": "sha512-NkKUXO0U118uzsmPrBkPbBAKl4zhLjgyq3qv2AUjaS+Vbz2pa8o4/bi8yqhx16ZDJgfWoSc6ko9FWsXrwDM26g==", + "dev": true + }, + "@edenjs/peg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@edenjs/peg/-/peg-1.0.2.tgz", + "integrity": "sha512-uUNcZ3dJCbuFDlq9cQXD0+zkCs6/seW2BFiW9XHYsokDfKDpP38S8/aWkevTF6hULzdNgfMIgtz/f/8A7uUOtA==", + "requires": { + "gulp-pegjs": "^0.1.0", + "gulp-rename": "^1.4.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "requires": { + "readable-stream": "~1.1.9" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint-config-airbnb": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.0.tgz", + "integrity": "sha512-R9jw28hFfEQnpPau01NO5K/JWMGLi6aymiF6RsnMURjTk+MqZKllCqGK/0tOvHkPi/NWSSOU2Ced/GX++YxLnw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^13.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-config-airbnb-base": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", + "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4" + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", + "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", + "dev": true, + "requires": { + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" + } + }, + "eslint-plugin-react": { + "version": "7.12.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", + "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1", + "object.fromentries": "^2.0.0", + "prop-types": "^15.6.2", + "resolve": "^1.9.0" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "requires": { + "sparkles": "^1.0.0" + } + }, + "gulp-pegjs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gulp-pegjs/-/gulp-pegjs-0.1.0.tgz", + "integrity": "sha1-gwFY7q6OcwFx1E3N6xyiDH83FOo=", + "requires": { + "gulp-util": "^3.0.6", + "object-assign": "^4.0.1", + "pegjs": "^0.10.0", + "through2": "^2.0.1" + } + }, + "gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==" + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "^3.0.3" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "requires": { + "lodash._root": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "requires": { + "duplexer2": "0.0.2" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react-is": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..da3ce34 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "wizbos-chatbot", + "description": "nothing yet", + "version": "1.0.8", + "dependencies": { + "@edenjs/peg": "^1.0.2", + "deep-equal": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + }, + "devDependencies": { + "eslint": "^6.0.1", + "@edenjs/eslint-config-eden": "^2.0.14", + "eslint-config-airbnb": "^17.1.1", + "eslint-plugin-import": "^2.18.0", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.14.2" + } +}