canoe
Overview
canoe provides a purely functional streaming interface over Telegram Bot API and allows you to build interactive chatbots using idiomatic Scala code.
Getting started
sbt dependency:
libraryDependencies += "org.augustjune" %% "canoe" % "<version>"
You can find the latest version in releases tab or by clicking on the maven-central badge.
Imports:
import canoe.api._
import canoe.syntax._
The problem
Building interactive chatbots requires maintaining the state of each conversation, with possible interaction across them and/or using shared resources. The complexity of this task grows rapidly with the advancement of the bot. canoe solves this problem by decomposing behavior of the bot into the set of scenarios which the chatbot will follow.
Example
Here's a quick example of how the definition of simple bot behavior looks like in canoe. More samples can be found here.
import canoe.api._
import canoe.syntax._
import cats.effect.ConcurrentEffect
import fs2.Stream
def app[F[_]: ConcurrentEffect]: F[Unit] =
Stream
.resource(TelegramClient.global[F](token))
.flatMap { implicit client => Bot.polling[F].follow(greetings) }
.compile.drain
def greetings[F[_]: TelegramClient]: Scenario[F, Unit] =
for {
chat <- Scenario.start(command("hi").chat)
_ <- Scenario.eval(chat.send("Hello. What's your name?"))
name <- Scenario.next(text)
_ <- Scenario.eval(chat.send(s"Nice to meet you, $name"))
} yield ()
Regardless of whether you decide to use scenarios for steering the bot, you are still able to use all functionality of Telegram Bot API in a streaming context, as it is demonstrated here.
Using webhooks
canoe also provides a support for obtaining messages from Telegram by setting a webhook. The same app described above would look this way using webhook version. Full example may be found here.
import canoe.api._
import canoe.syntax._
import cats.effect.ConcurrentEffect
import fs2.Stream
val url: String = "<your server url>"
def app[F[_]: ConcurrentEffect: Timer]: F[Unit] =
Stream
.resource(TelegramClient.global[F](token))
.flatMap { implicit client =>
Stream.resource(Bot.hook[F](url)).flatMap(_.follow(greetings))
}
.compile.drain
def greetings[F[_]: TelegramClient]: Scenario[F, Unit] = ??? // Scenario stays unchanged
Using a webhook version you have to specify the url
to which Telegram messages will be sent.
This address must be reachable for the Telegram,
so in case you're using your local environment you'd have to expose your local host to the Internet.
It can be achieved using ngrok simply following this comprehensive guide.