@hackage marvin0.1.1

A modular chat bot

Marvin, the paranoid bot (⍺ stage)

Travis Hackage

Marvin is an attempt to combine the ease of use of hubot with the typesafety and easy syntax of Haskell and the performance gains from compiled languages.

The in-depth documentation can be found on readthedocs https://marvin.readthedocs.org

Installation

You can get a release version of marvin on Hackage.

However this library is still a very early stage so you might want to get updates quicker. You can do so by using stack and adding a recent commit of this repository to your stack.yaml file. Stack will take care of downloading and building it for you.

TLDR

module MyScript where

import Marvin.Prelude

script :: IsAdapter a => ScriptInit a
script = defineScript "my-script" $ do
    hear "sudo (.+)" $ do
        match <- getMatch

        reply $(isL "All right, i'll do #{match !! 1}")
    
    respond "open the (\\w+) door" $ do
        match <- getMatch
        let door = match !! 1
        openDoor door
        send $(isL "Door #{door} opened")
    
    respond "what is in file (\\w+)\\??" $ do
        match <- getMatch 
        let file = match !! 1

        contents <- liftIO $ readFile file

        send contents

How to Marvin

The best way to use Marvin is very much taken from hubot.

A Marvin instance composes of a collection of scripts which are reactions or actions on certain messages posted in slack. Each script is a Haskell source file. They get compiled into one single static binary, which acts depending on the adapter used, in the case of the slack real time messaging adapter for instance it opens a websocket to the slack server from which it recieves events as they happen in your chat application.

Defining scripts

Defining scripts is very easy.

Create a new Haskell source file like "MyScript.hs" and import marvins prelude Marvin.Prelude. This provides you with all the tools you need to interact with marvin.

Now you can start to define your script with defineScript which produces a script initializer. If you wish to use marvins automatic script discovery your script initializer should be named script

module MyScript where

import Marvin.Prelude

script :: IsAdapter a => ScriptInit a
script = defineScript "my-script" $ do
    ...

The script id, "my-script" in this case, is the name used for this script when repoting loggin messages as well as the key for this scripts configuration, see configuration.

In the define script block you can have marvin react to certain events with hear and respond. More information on those in the section reacting

Finally after you have defined your scripts you have to tie them together. You can do this manually or you can have marvin create the boilerplate code for you.

To do this simply place a main file (this is the file you'll be compiling later) in the same directory the scripts are placed in. Leave the file empty except for this line at the top {-# OPTIONS_GHC -F -pgmF marvin-pp #-}. When you compile the file marvin will look for any other ".hs" and ".lhs" files in the same directory, import them and define a server which runs with the script from each. If you wish to hide a file from the auto discovery either place it in a different directory or prefix it with "." or "_".

Reacting

There are two main ways (currently) of reacting to events, hear and respond.

hear is for matching any incoming message. The provided regex is tried against all incomming messages, if one matches the handler is called.

repond only triggers on message which have the bot name, or a case variation thereof as the first word.

Once a handler has triggered it may perform arbitrary IO actions (using liftIO) and send messages using reply and send.

  • reply addresses the message to the original sender of the message that triggered the handler.
  • send sends it to the same Channel the tiggering message weas sent to.
  • messageChannel sends a message to a Channel specified by the user.

Configuration

Configuration for marvin is written in the configurator syntax.

Configuration pertaining to the bot is stored under the "bot" key.

bot {
    name = "my-bot"
    logging = "INFO"
}

By default each script has access to a configuration stored under script.<script-id>. And of course these scripts can have nested config groups.

bot {
    name = "my-bot"
}

script {
    script-1 {
        some-string = "foo"
        some-int = 1337
        bome-bool = true
    }
    script 2 {
        nested-group {
            val = false
        }
        name = "Trump"
        capable = false
    }
}

Configuration pertaining to the adapter is stored under adapter.<adapter-name>

bot {
    name = "my-bot"
    logging = "INFO"
}
adapter {
    slack-rtm {
        token = "eofk"
    }
}

Wiring manually

How Marvin interacts with your chat program depends on the used Adapter. For instance the currently default slack-rtm adapter creates a (client) websocket connection with the slack API and listens to the events there. Other adapters may require to set up a server.

Utilities

All these utilities are already available to you if you import Marvin.Prelude.

Regex

Implemented in Marvin.Util.Regex, documentation coming soon.

Mutable variables

Implementation started in Marvin.Util.Mutable, documentation coming soon.

Format strings

Marvins Prelude exposes the marvin-interpolate library, which enables the user to write CoffeeScript/Scala like interpolated Strings using Template Haskell.


str = let x = "Hello" in $(isL "#{x} World!")
-- "Hello World"

JSON

Exposed in Marvin.Util.JSON documentation coming soon. Until then refer to aeson.

Logging

Marvin comes with a logging facility built in. Marvin.Util.Logging expose the logging facility. Several functions are available, depending on the urgency of your message, like logError, logInfo and logWarn. Logging messages made this way are automatically formatted and tagged with the scripts that reported them.

By default all logging messages with higher priority NOTICE or higher are shown. Using the command line parameter verbose also adds INFO messages and debug adds DEBUG messages. You can select the exact logging level in your config file (see also configuration).

Random

Implemented in Marvin.Util.Random, documentation coming soon.

HTTP

Coarsely implemented in Marvin.Util.HTTP, documentation coming soon.