npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hubot-command-mapper

v9.0.0

Published

Helps with mapping tools and commands to hubot

Downloads

344

Readme

hubot-command-mapper

Helps with the mapping of tools and commands for your Hubot. No more regex. No more tool restarts during development.

npm version License: MIT

Why? Writing regular expressions for commands is hard. You have to spend some time to prevent expressions from colliding with others. Now the mapper takes care of that problem.

Installation

Install the command mapper like this:

npm install hubot-command-mapper --save

A simple example

Let's define the clear screen command by replying with 48 space-lines:

const { map_command } = require("hubot-command-mapper")

module.exports = robot => {
  map_command(context.robot, "clear screen", options, context => {
    for (let i = 0; i < 48; i++) {
      context.res.emote(" ")
    }
  })
}

The mapper will map the command into the robot using the respond method. The hear method is currently not supported.

A complexer example

Let's group a few actions into a tool. A tool has a name and a collection of named commands. They are mapped in such a way that the actions will not collide. Here we've created a todo list with the add, list and remove actions:

const { map_tool, RestParameter } = require("hubot-command-mapper")

module.exports = robot => {
  let todos = []

  map_tool(robot, {
    name: "todo",
    commands: [
      {
        name: "add",
        alias: [""],
        parameters: [new RestParameter("item")],
        execute: context => {
          todos.push(context.values.item)
          context.res.reply(`Added _${context.values.item}_ to the list.`)
        }
      },
      {
        name: "list",
        alias: [""],
        execute: context => {
          if (!todos.length) context.res.reply("The list is empty.")
          else {
            let txt = "The following item(s) are on the list:\n"
            txt += todos.map(t => "- " + t).join("\n")
            context.res.reply(txt)
          }
        }
      },
      {
        name: "remove",
        alias: ["del", "rm"],
        parameters: [new RestParameter("item")],
        execute: context => {
          const f = context.values.item.toLowerCase()
          const a = todos.filter(t => t.toLowerCase().indexOf(f) == -1)
          const x = todos.length - a.length
          todos = a
          context.res.reply(`Removed ${x} item(s) from the list.`)
        }
      }
    ]
  })
}

Alias

Sometimes you want to have more control over the way commands and tools are presented to the user. The rigid control you need as a developer does not have to end up in your tool. You can use the alias and map_default_alias to route messages to your tools and commands.

Alias will take a map and route accordingly. Please use * to match more than the alias itself.

// imagine you have the following commands:
// @bot todo list: lists all the items on the todo list
// @bot todo add {item}: adds the item to the list

const { alias } = require("hubot-command-mapper")

module.exports = robot => {
  alias(robot, {
    list: "todo list",
    "todo*": "todo add"
  })
}

But what if you want to route all messages that are not handled by tools to a certain action? Use map_default_alias. You can provide regular expressions for messages that should be skipped.

// imagine you have the following commands:
// @bot search `query`: searches the database

const { alias } = require("hubot-command-mapper")

module.exports = robot => {
  map_default_alias(robot, "search", /help/i)
}

Capturing with named parameters

A capture can be done in two ways. The first way is providing a regular expression-string as the capture. The values can be accessed through the match object in the execute:

const { mapper } = require("hubot-command-mapper")

module.exports = robot => {
  const tool = {
    name: "count",
    commands: [
      {
        name: "from",
        capture: "(\\d+) to (\\d+)",
        execute: context => {
          // because an alias might be used, we need to select
          // from the end of the matches.
          const a = Number(context.match[context.match.length - 2])
          const b = Number(context.match[context.match.length - 1])

          for (let i = a; i < b + 1; i++) {
            context.res.reply(`${i}!`)
          }
        }
      }
    ]
  }

  mapper(robot, tool)
}

Another way is using named parameters. The values can be accessed by the values object. Each value is added as a named property.

const { mapper, StringParameter } = require("hubot-command-mapper")

module.exports = robot => {
  const tool = {
    name: "norris",
    commands: [
      {
        name: "impersonate",
        parameters: [new StringParameter("firstName"), new StringParameter("lastName")],
        execute: context => {
          const firstName = context.values.firstName
          const lastName = context.values.lastName

          context.res.reply(`${firstName} ${lastName} has counted to infinity. Twice!`)
        }
      }
    ]
  }

  mapper(robot, tool)
}

The following parameters are available:

| Parameter type | Example | Purpose | | ----------------- | ---------------------------------------------- | ------------------------------------------------------------------------------ | | StringParameter | new StringParameter("name") | Adds a string parameter | | StringParameter | new StringParameter("name", "Chuck Norris") | Adds an optional string parameter that default to "Chuck Norris" | | StringParameter | new StringParameter("name") | Adds a string parameter | | ChoiceParameter | new ChoiceParameter("name", ["a", "b", "c"]) | Adds a choice parameter that matches "a", "b" or "c" | | RestParameter | new RestParameter("rest) | Captures the rest of the message. Used to capture anything else until the end. | | TokenParameter | new TokenParameter("source") | Captures a token. A token must be present in the string. | | IPv4Parameter | new IPv4Parameter("ip") | Matches an IP version 4 address. |

Extra commands

Each tool has a view extra commands that will be added by the mapper:

  • @hubot tool-name debug: will show which regular expressions are mapped
  • @hubot tool-name help: will show the help of the tool, read from the original script file. It uses the help feature of Hubot.

Full specification

A tool is an object with the following:

  • name:string: the name of the tool. Required.
  • commands:Array<Command>: The commands that are supported by the tool. Only tools with at least 1 command can be mapped. Required.
  • auth:Array<string>: Used for user-name based authorization. Only the specified users may access the tool. Optional.

A command has the following specification:

  • name:string: the name of the command. Required.
  • execute(context): void : Called when the command is invoked. The context has the scope in which the command was called. The match contains captured information. Required. context.tool : ITool - the tool that executed the command. context.robot - the robot. context.res - the response.context.match - the result of the regular expression matches.context.values - an object with named values. Correspond to the parameter values.
  • alias:Array<string>: A list of aliases of the command. Can be used to support multiple names to trigger the command like: ["del", "rm"]. An empty alias is also possible to add default commands to tools. Optional.
  • auth:Array<string>: A list of usernames that will be used to authorize access to the command. Optional.
  • capture:string: A regex that can be used to match values behind a command. Optional.
  • parameters:[IParameter]: a list of named parameters. The value of each parameter is added to the values object of the invoked callback. Possible values: StringParameter, NumberParameter, FractionParameter, RegExParameter and RestParameter.

Middleware

Some users will use software keyboards on Slack with auto prediction. Some of those will insert an extra space after an auto predicted space. This will cause many commands to fail - as regex mapping tends to be very strict.

Also: some users copy/paste markdown bold or italic texts.

To counter this problem we've created a some middleware that will remove trailing whitespace characters from each message. It can be hooked up like this:

const {
  removeMarkdownFromIncomingMessages
  removeTrailingWhitespaceCharactersFromIncomingMessages,
  removeTrailingBotWhitespaceCharactersFromIncomingMessages,
} = require("hubot-command-mapper")

module.exports = robot => {
  removeMarkdownFromIncomingMessages(robot)
  removeTrailingWhitespaceCharactersFromIncomingMessages(robot)
  removeTrailingBotWhitespaceCharactersFromIncomingMessages(robot)
}

Move feature to another bot

As bots grow, you might want to move features from one bot to another. You can add the replacedByBot option:

const { map_command } = require("hubot-command-mapper")

module.exports = robot => {
  map_command(robot, "clear screen", { replacedByBot: "kz" }, context => {
    // remove code
  })
}

This will print:

@user Sorry, this feature has been replaced by @kz. Please use:

@kz clear screen

Want to contribute?

Cool! We're using the following setup:

  • Visual Studio Code
  • TypeScript: compiles to ./dist
  • Mocha and Chai for testing: npm test
  • Update dependencies: npm run update-packages

Contributions:

  1. Create a branch
  2. Create unit test(s) for the change(s)
  3. Submit a pull request
  4. Contact @KeesTalksTech if we don't respond in time.

Happy coding!