From 3b9ab50e7f8fca7a8c7622bac62f94cb93ca6601 Mon Sep 17 00:00:00 2001 From: Vavaballz Date: Fri, 14 Apr 2017 15:09:49 +0200 Subject: [PATCH] Add command module --- .babelrc | 15 +++ .flowconfig | 8 ++ .gitignore | 134 ++++++++++++++++++++++++ LICENSE | 3 +- example/index.js | 43 ++++++++ package.json | 25 +++++ src/Commands/Builders/CommandBuilder.js | 82 +++++++++++++++ src/Commands/Builders/GroupBuilder.js | 43 ++++++++ src/Commands/Command.js | 104 ++++++++++++++++++ src/Commands/CommandManager.js | 65 ++++++++++++ src/Krobotjs.js | 6 ++ 11 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 .babelrc create mode 100644 .flowconfig create mode 100644 .gitignore create mode 100644 example/index.js create mode 100644 package.json create mode 100644 src/Commands/Builders/CommandBuilder.js create mode 100644 src/Commands/Builders/GroupBuilder.js create mode 100644 src/Commands/Command.js create mode 100644 src/Commands/CommandManager.js create mode 100644 src/Krobotjs.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..91a0275 --- /dev/null +++ b/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": ["stage-2", ["env", { + "targets": { + "node": "current" + }, + "exclude": ["transform-async-to-generator"] + }]], + "plugins": [ + "transform-flow-strip-types", + "syntax-flow", ["flow-runtime", { + "assert": true, + "annotate": true + }], "transform-decorators-legacy" + ] +} \ No newline at end of file diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..6c76730 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,8 @@ +[ignore] +.*/node_modules/.* + +[include] + +[libs] + +[options] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b1efda --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ + +# Created by https://www.gitignore.io/api/node,intellij,visualstudiocode + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# End of https://www.gitignore.io/api/node,intellij,visualstudiocode + +lib/ +.vscode/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9cecc1d..9e2b7e6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ +<<<<<<< HEAD GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -671,4 +672,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000..7e30547 --- /dev/null +++ b/example/index.js @@ -0,0 +1,43 @@ +const Discord = require('discord.js'); +const { + CommandManager +} = require('../lib/Krobotjs'); + +const bot = new Discord.Client(); +const commands = new CommandManager({ + parse: [{ + // Match user (<@ID>) + match(message, arg) { + return (/^<@((\d)+)>$/g).test(arg) + }, + // Replace it with an instance of Discord.User(ID) + async perform(message, arg) { + const gmember = await message.channel.guild.fetchMember((/^<@((\d)+)>$/g).exec(arg)[1]); + return gmember.user; + } + }] +}); +// Create a command group +commands.group().prefix("#").apply(_ => { + commands + .command("test [opt]", (message, args) => message.reply('test')).register() + .sub("moche", (message, args) => message.reply('t es moche')).register(); + commands + .command("lol ", (message, args) => message.reply('lol')).register() + .sub("test", (message, args) => message.reply('lol test')).register(); + commands + .command("test2 ", (message, args) => message.reply(args.get('message'))).register() + .sub("moche", (message, args) => message.reply('t es moche 2')).register(); +}); + +bot.on('message', message => { + // If the message is a command, then it will be executed and will return "true" + // yet, if there is no command, it will return "false" + if (!commands.dispatch(message)) + // continue actions +}); +bot.on('ready', _ => console.log('Connected')); +bot.on('reconnecting', _ => console.log('Reconnecting')); +bot.on('error', error => console.error(error)); + +bot.login(process.env.DISCORD_TOKEN); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d96e9fc --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "krobotjs", + "version": "1.0.0", + "description": "A library that helps you create a discord bot with DiscordJS", + "main": "lib/Krobotjs.js", + "scripts": { + "build": "babel src/ -d lib/", + "prepublish": "npm run build" + }, + "author": "Vavaballz", + "license": "GPL-3.0", + "dependencies": { + "discord.js": "^11.0.0" + }, + "devDependencies": { + "babel-cli": "^6.24.1", + "babel-plugin-flow-runtime": "^0.10.0", + "babel-plugin-syntax-flow": "^6.18.0", + "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-flow-strip-types": "^6.22.0", + "babel-preset-env": "^1.3.3", + "babel-preset-stage-2": "^6.24.1", + "flow-runtime": "^0.10.0" + } +} \ No newline at end of file diff --git a/src/Commands/Builders/CommandBuilder.js b/src/Commands/Builders/CommandBuilder.js new file mode 100644 index 0000000..afcf934 --- /dev/null +++ b/src/Commands/Builders/CommandBuilder.js @@ -0,0 +1,82 @@ +const Command = require('../Command'); + +class CommandBuilder { + + constructor(commandManager) { + this._commandManager = commandManager; + this._prefix = ''; + this._middlewares = []; + this._command = ''; + this._args = []; + this._parent = null; + this._handler = null; + } + + parent(parent) { + this._parent = parent; + return this; + } + + middleware(middleware: Function | Array) { + if (typeof middleware === 'function') + this._middlewares.push(middleware); + else if (middleware instanceof Array) + this._middlewares = this._middlewares.concat(middleware); + return this; + } + + prefix(prefix: string) { + if (!this._parent || (this.parent && !this._parent.prefix)) + this._prefix = prefix; + return this; + } + + command(cmd: string) { + const split = cmd.split(' '); + this._command = split.shift(); + split.forEach(arg => { + let optional = false; + let list = false; + let regex = new RegExp('', 'g'); + + if (arg === '*') arg = '[dynamic...]'; + if (arg[0] === '[') optional = true; + + arg = arg.slice(1, arg.length - 1); + + if (arg.includes(':')) + [arg, regex] = arg.split(':') + regex = new RegExp(regex, 'g'); + + if (arg.endsWith('...')) + [list, arg] = [true, arg.replace('...')] + + this._args.push({ + key: arg, + optional, + list, + regex + }); + }); + return this; + } + + handler(handler: Function) { + this._handler = handler; + return this; + } + + build() { + return new Command(this._prefix + this._command, this._args, this._middlewares, this._handler); + } + + register() { + const cmd = this.build(); + if (this._parent) this._parent.sub(cmd); + else this._commandManager.register(cmd); + return cmd; + } + +} + +module.exports = CommandBuilder; \ No newline at end of file diff --git a/src/Commands/Builders/GroupBuilder.js b/src/Commands/Builders/GroupBuilder.js new file mode 100644 index 0000000..26c1b1d --- /dev/null +++ b/src/Commands/Builders/GroupBuilder.js @@ -0,0 +1,43 @@ +// @flow +class GroupBuilder { + + constructor(commandManager, defaultPrefix) { + this._commandManager = commandManager; + this._middlewares = []; + this._prefix = defaultPrefix; + this._parent = null; + } + + parent(parent) { + console.log(parent) + this._parent = parent; + return this; + } + + prefix(prefix: string) { + if (!this._parent || (this.parent && !this._parent.prefix)) + this._prefix = prefix; + return this; + } + + middleware(middleware: Function | Array) { + if (typeof middleware === 'function') + this._middlewares.push(middleware); + else if (middleware instanceof Array) + this._middlewares.concat(middleware); + return this; + } + + apply(cb: Function) { + this._commandManager.push({ + prefix: this._prefix, + middlewares: this._middlewares, + parent: this._parent + }); + cb(); + this._commandManager.pop(); + } + +} + +module.exports = GroupBuilder; \ No newline at end of file diff --git a/src/Commands/Command.js b/src/Commands/Command.js new file mode 100644 index 0000000..c519f18 --- /dev/null +++ b/src/Commands/Command.js @@ -0,0 +1,104 @@ +class Command { + + constructor(command, args, middlewares, handler) { + this._command = command; + this._args = args; + this._middlewares = middlewares; + this._handler = handler; + this._subs = []; + } + + async call(parser, message, args) { + let subCalled = false; + if (args.length > 0) { + this._subs.forEach(sub => { + if (sub.command === args[0]) { + if (this.callMiddlewares(message)) { + sub.call(message, args.slice(1)); + subCalled = true; + } + return; + } + }); + } + if (!subCalled) { + const map = new Map(); + for (let i = 0; i < this._args.length; i++) { + let argument = args[i]; + + if (!this._args[i].optional && !argument) return; + + if (this._args[i].list) { + argument = args.splice(i) + let match = true; + argument.every(val => { + if (this._args[i].regex.test(val)) return true; + else return match = false; + }); + if (!match) return; + } else { + const test = this._args[i].regex.test(argument); + if (!test) return; + if (i + 1 === this._args.length && i + 1 < args.length) + return; + } + + if (parser && parser instanceof Array) + for (let i = 0; i < parser.length; i++) { + const parse = parser[i]; + let cond = false; + if (parse.match instanceof RegExp) + cond = parse.match.test(argument) + else if (typeof parse.match === 'function') + cond = parse.match(message, argument) + if (cond && typeof parse.perform === 'function') { + let res = parse.perform(message, argument) + if (res instanceof Promise) + res = await res; + argument = res; + } + }; + + map.set(this._args[i].key, argument) + } + + if (this.callMiddlewares(message, map)) + this._handler(message, map); + } + } + + callMiddlewares(message) { + let ret = true; + this._middlewares.every(middleware => { + return !middleware(this, message) ? ret = false : true; + }); + return ret; + } + + sub(cmd: string | Command, handler: Function = () => null) { + const CommandBuilder = require('./Builders/CommandBuilder'); + if (cmd instanceof Command) + this._subs.push(cmd) + else if (typeof cmd === 'string' && typeof handler === 'function') + return (new CommandBuilder(null)).parent(this).command(cmd).handler(handler); + } + + get command() { + return this._command + } + get args() { + return this._args + } + get middlewares() { + return this._middlewares + } + get handler() { + return this._handler + } + get subs() { + return this._subs + } + +} + +module.exports = Command; \ No newline at end of file diff --git a/src/Commands/CommandManager.js b/src/Commands/CommandManager.js new file mode 100644 index 0000000..68b5a98 --- /dev/null +++ b/src/Commands/CommandManager.js @@ -0,0 +1,65 @@ +const GroupBuilder = require('./Builders/GroupBuilder'); +const CommandBuilder = require('./Builders/CommandBuilder'); + +const typeConf = { + parse: [] +}; + +class CommandManager { + + constructor(config) { + this._config = Object.assign({}, typeConf, config); + this._commands = []; + this._defaultPrefix = '/'; + this._groupStack = []; + } + + group() { + const group = new GroupBuilder(this, this.defaultPrefix); + if (this._groupStack.length > 0) group.parent(this._groupStack[this._groupStack.length - 1]); + return group; + } + + command(command: string, handler: Function) { + const builder = new CommandBuilder(this); + let prefix = this._defaultPrefix; + this._groupStack.forEach(group => { + prefix = group.prefix || this._defaultPrefix; + if (group.parent) builder.parent(group.parent) + builder.middleware(group.middlewares); + }); + return builder.prefix(prefix).command(command).handler(handler); + } + + register(command) { + this._commands.push(command) + } + + push(group) { + this._groupStack.push(group) + } + + pop() { + this._groupStack.pop() + } + + get defaultPrefix() { + return this._defaultPrefix + } + + dispatch(message) { + const splitWithQuotes = text => text.match(new RegExp("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'", 'g')).map(val => val.replace(/\"|\'/g, '')); + const lines = splitWithQuotes(message.content); + if (lines.length == 0) return; + this._commands.forEach(cmd => { + if (cmd.command === lines[0]) { + cmd.call(this._config.parse, message, lines.splice(1)); + return true; + } + }); + return false; + } + +} + +module.exports = CommandManager; \ No newline at end of file diff --git a/src/Krobotjs.js b/src/Krobotjs.js new file mode 100644 index 0000000..efdd923 --- /dev/null +++ b/src/Krobotjs.js @@ -0,0 +1,6 @@ +module.exports = { + CommandManager: require('./Commands/CommandManager'), + Command: require('./Commands/Command'), + GroupBuilder: require('./Commands/Builders/GroupBuilder'), + CommandBuilder: require('./Commands/Builders/CommandBuilder'), +} \ No newline at end of file