Bot API for Scala

License CircleCI Codacy Badge Maven Central

This is simple framework for easy develop bots in FSM style on Scala language based on Akka. For now it's support Slack, Skype and Telegram messengers.

Getting started.

For the beginning you need to add dependency for your SBT project:

libraryDependencies += "com.github.kerzok" %% "scalabotapi" % "0.2.8"

Also you need to create bot on different providers:

After that you will can make your own bot.

Writing your first bot.

A simple echo bot

The AbstractBot trait handle all messages from users and send it to conversations by different criteria.

First of all we need to create new file called EchoBot.scala and inherits from AbstractBot. AbstractBot apply Data class as generic argument. As we need no any additional data we use EmptyData class.

class EchoBot extends AbstractBot[EmptyData] {
  override protected var data: EmptyData = EmptyData()
    
  override protected def id: String = ???

  override def printHelp: String = ???

  override def printUnknown: String = ???

  override def startConversation: PartialFunction[Intent, Conversation] = ???
}

After that we need specify id which need for configuration and also add help and unknown text.

  override protected def id: String = "EchoBot"

  override def printHelp: String = "This is example echo bot"

  override def printUnknown: String = "I don't understand you"

startConversation is a function which handle all incoming messages and select suitable conversation to handle it. In our case we need to handle all text messages.

  override def startConversation: PartialFunction[Intent, Conversation] = {
    case intent: TextIntent => EchoConversation()(intent)
  }

EchoConversation is the class inherited from Conversation which contains states like FSM. It applies Intent and return new BotState with optionally reply to user. Our bot will have only one BotState.

case class EchoConversation() extends Conversation {
    override def initialState: BotState = BotState {
      case intent: TextIntent => Reply(Exit).withIntent(ReplyMessageIntent(intent.sender, intent.text))
    }
} 

Exit is the system BotState which finish conversation and turn user chat to Idle.

Configuration EchoBot

In configuration file we need to define API keys and tokens for messengers to connect.

EchoBot {
  TelegramSource {
    id = "Your Telegram API token here"
  }
  SlackSource {
    id = "Your Slack API token here"
  }
  SkypeSource {
    id = "Your Skype API token here"
    secret = "Your Skype secret here"
  }
}

Also we need to add to specify some Akka Persistence parameters needs for internal use.

akka.persistence.journal.plugin = "akka.persistence.journal.leveldb"
akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"

akka.persistence.journal.leveldb.dir = "target/example/journal"
akka.persistence.snapshot-store.local.dir = "target/example/snapshots"

As the result we can run our EchoBot.

object EchoBotMain {
  BotHelper.registerBot(classOf[EchoBot])
}

More examples you can find Here.

Extensions

Extensions is a special mixin for AbstractBot which contains additional functionality. There is several extensions available for use:

###Scheduler Extension Scheduler extension provides job scheduler based on Quartz Job Scheduler. Job can be set by cron like expression or duration expressions. Scheduler extension provides special type of intent — SchedulerIntent — which is send to the bot system as usual intent every time the scheduler triggered.

Example:

val schedulerState = BotState {
    ...
    repeat(s"0 */2 * ? * *", SchedulerIntent("job1", "Hello")) //Will send SchedulerIntent("job1", "Hello") to the bot system every 2 minutes
    repeatEvery(3 minutes, SchedulerIntent("job2", "World")) //Will send SchedulerIntent("job2", "World") to the bot system every 3 minutes 
    ... 
    Reply(Exit)    
}

You can catch this Intent in startConversation method.

override def startConversation: PartialFunction[Intent, Conversation] = {
    ...
    case intent@SchedulerIntent(_, "Hello") => SchedulerHandlerConversation()(intent)
    ...
}

To cancel job use delete("jobId") method.

delete("job1")

###Socket Extension Socket extension provides requests to the remote servers. To use this extension you need to call makeRequest method with SocketIntent which contains params:

  • sender — Chat which will be send the result
  • url — Url to call
  • requestParams — Parameters of the request

Example:

val params = RequestParams(canCache = true).put("streamId", "feed/https://bash.org.ru/rss/")
makeRequest[BashResponse](SocketIntent(intent.sender, "http://cloud.feedly.com/v3/streams/contents", params))

The answer will be sent to the system and can be processed in BotState handler .

val responseFromBashHandler: BotState = BotState {
  case ResultIntent(sender, result: BashResponse) =>
    val itemNumber = Random.nextInt(result.items.size)
    val article = result.items(itemNumber)
    Reply(Exit)
      .withIntent(ReplyMessageIntent(sender, s"<b>${article.title.trim}</b>\n${article.summary.content.trim.replaceAll("<br>", "\n")}"))
}

###TextRazor Extension TextRazor extension is Natural Language Processing Extension (NLP Extension) for improve parsing commands based on TextRazor API. This extension build dependency tree of user command and can be matched with pattern.

To configure TextRazor you need to specify razorApiKey value in code of your bot.

Example:

override def startConversation: PartialFunction[Intent, Conversation] = {
    case intent@TextIntent(_, text) if text.nlpMatch("add|create" -> "team" -> "new") => 
        CreateTeamConversation(data)(intent)  // Applies expressions like "Create new team", "Please, add new team", "Can you create new team", etc.
    case intent@TextIntent(_, text) if text.nlpMatch("teams" -> "manage") => 
        ManageTeamsConversation(data)(intent) // Applies expressions like "Menage teams", "Can you manage teams", etc
    case intent@TextIntent(_, text) if text.nlpMatch("join" -> * -> "team") => 
        JoinTeamConversation(data)(intent)    // Applies expressions like "Join SomeTeam team" or something like this
    ...
}

To write your own extension you need to inherit from Extension trait.

##Intents Intents is a part of data which is used for communicate between part of the bot system and users. There are several types of default intent, but you can create your own intent:

TextIntent — Simple wrap of user's text sent to bot.

PositiveIntent — User's positive answer of a question (e.g. "yes").

NegativeIntent — User's negative answer of a question (e.g. "no").

NumberIntent — User's number answer. Use when user need to select one of the few answers.

AskChangeStateIntent — Request to change state of other user (Can be declined if canChange parameter of the BotState is true).

RequireChangeStateIntent — Requires to change state of other user (Can't be declined).

SystemPositiveIntent — System positive answer of some request (e.g. ask for change state of other user).

SystemNegativeIntent — System negative answer of some request (e.g. ask for change state of other user).

ReplyMessageIntent — Send some text to user (usually use when bot state is changed).

ScheduleIntent — Message sent by SchedulerExtension when scheduler triggered.

SocketIntent — Intent for SocketExtension which specify parameters for HTTP request.

SocketReply — Message sent by SocketExtension which contains the result of HTTP request.

You can create your own Intent by inherited Intent trait and parse it by overriding handleCustomIntent method.