This page was originally created for a JUGs workshop.
The workshop is about the first steps on implementing a chat bot with:
The following chapters document the workshop step by step.
A basic understanding of SBT and Scala will help;).
/newbot
git clone https://github.com/pme123/play-scala-telegrambot4s.git
The master branch contains only:
libraryDependencies += "info.mukel" %% "telegrambot4s" % "3.0.14"
(Here the Solution Branch if you have problems)
Let's use the provided JSON-API from Telegram directly. We only wan't to show the info about our bot on the index page.
First we want to provide the token in a safe way to avoid leaking it.
bots
package and add:lazy val botToken: String = scala.util.Properties
.envOrNone("BOT_TOKEN")
.getOrElse(Source.fromResource("bot.token").getLines().mkString)
bot.token
with your token and put it in your conf
folder.lazy val botToken = "[BOT_TOKEN]"
Implement the web-service client:
libraryDependencies += ws
HomeController
:class HomeController @Inject()(cc: ControllerComponents
, ws: WSClient)
(implicit ec: ExecutionContext)
lazy val url = s"https://api.telegram.org/bot$botToken/getMe"
HomeController.index()
function: def index() = Action.async { implicit request: Request[AnyContent] =>
ws.url(url)
.get() // as this returns a Future the function is now async
.map(_.json) // the the body as json
.map(_.toString())
.map(str => Ok(views.html.index(str))) // forward the result to the index page
}
@(myBot: String)
@main("Telegram Bots rock!") {
<h1>Welcome to our Bot!</h1>
<p>@myBot</p>
}
sbt
run
{"ok":true,"result":{"id":301276637,"is_bot":true,"first_name":"MeetupDemoBot","username":"MeetupDemoBot"}}
That's it - let's move now to the Scala API. For the next Bot examples we don't need the Play server - so you can shut it down.
(you need to update the HomeControllerSpec - or if you are lazy remove it)
Before we start let's explain shortly the difference between Webhooks and Polling. This is the quote from the Scala API:
Both methods are fully supported. Polling is the easiest method; it can be used locally without any additional requirements. It has been radically improved, doesn't flood the server (like other libraries do) and it's pretty fast.
Using webhooks requires a server (it won't work on your laptop). For a comprehensive reference check Marvin's Patent Pending Guide to All Things Webhook.
So for this workshop, or examples in general Polling is the way to go.
Let's greet the Bot and it should return that with a personalized greeting.
HelloBot
object HelloBot
extends TelegramBot // the general bot behavior
with Polling // we use Polling
with Commands { // and we want to listen to Commands
lazy val token: String = botToken // the token is required by the Bot behavior
onCommand('hello) { implicit msg => // listen for the command hello and
reply(s"Hello ${msg.from.map(_.firstName).getOrElse("")}!") // and reply with the personalized greeting
}
}
object BotApp extends App {
HelloBot.run()
}
BotFather
to create our command: /setcommands
botcommands.txt
:hello - Simple Hello World.
BotFather
will help with this./
in the text-field).(Here the Solution Branch if you have problems)
The first step to implement a conversation with a user is to understand the concept of callbacks
. To guide the user through a conversation you can provide a keyboard. These keys (buttons) are identified with a callback identifier.
Create a Scala class CounterBot
in the bots
package (you can copy the HelloBot
:
/counter
to start the process: onCommand("/counter") { implicit msg =>
reply("Press to increment!", replyMarkup = Some(markupCounter(0)))
}
private def markupCounter(n: Int): InlineKeyboardMarkup = {
requestCount += 1
InlineKeyboardMarkup.singleButton( // set a layout for the Button
InlineKeyboardButton.callbackData( // create the button into the layout
s"Press me!!!\n$n - $requestCount", // text to show on the button (count of the times hitting the button and total request count)
tag(n.toString))) // create a callback identifier
}
private val TAG = "COUNTER_TAG"
private def tag: String => String = prefixTag(TAG)
onCallbackWithTag(TAG) { implicit cbq => // listens on all callbacks that START with TAG
// Notification only shown to the user who pressed the button.
ackCallback(Some(cbq.from.firstName + " pressed the button!"))
// Or just ackCallback() - this is needed by Telegram!
for {
data <- cbq.data //the data is the callback identifier without the TAG (the count in our case)
Extractors.Int(n) = data // extract the optional String to an Int
msg <- cbq.message
} /* do */ {
request(
EditMessageReplyMarkup( // to update the existing button - (not creating a new button)
Some(ChatId(msg.source)), // msg.chat.id
Some(msg.messageId),
replyMarkup = Some(markupCounter(n + 1))))
}
}
object CounterBotApp extends App {
CounterBot.run()
}
counter - Counts the time a User hits the button.
like above. As the commands are set always in one step it makes sense to manage them in file. Create 'bot-commands.txt` file and add: hello - Simple Hello World.
counter - Counts the time a User hits the button.
CounterBotApp
and select the command /counter
/counter
This was the basic workshop. Now we want to do complexer conversations. To get to this next level we need quite some ingredients:
Here starts the setup with Play to make more sense. As we use and integrate the FSM provided with Akka.
I created another project for an example: play-akka-telegrambot4s-incidents
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4