Marvin, the paranoid bot (⍺ stage)

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.
A more in-depth version of the contents of this readme can be found on the wiki.
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 "I|i can't stand this (\w+)" $ do
match <- getMatch
let thing = match !! 1
reply $ "I'm sorry to tell you but you'll have to do " ++ thing
respond "open the (\w+) door" $ do
match <- getMatch
let door = match !! 1
openDoor door
send $ printf "Door %v opened" door
respond "what is in file (\w+)" $ do
match <- getMatch
let file = match !! 1
liftIO $ readFile file
send file
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 is a HTTP server that listens for slack's event calls.
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 room the tiggering message weas sent to.
messageRoom
sends a message to a room 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.
For String formatting Marvin re-exposes the Text.Printf
module.
Format strings use placeholders with %
, the default formatter (works for all Show
datatypes) is %v
.
Substitution is done with the varargs function printf
.
You can find the full documentation in the documentation for the Text.Printf
module.
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 errorM
, infoM
and criticalM
.
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.