Step 1. Add the JitPack repository to your build file
Add it in your root settings.gradle at the end of repositories:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Add it in your settings.gradle.kts at the end of repositories:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Add to pom.xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Add it in your build.sbt at the end of resolvers:
resolvers += "jitpack" at "https://jitpack.io"
Add it in your project.clj at the end of repositories:
:repositories [["jitpack" "https://jitpack.io"]]
Step 2. Add the dependency
dependencies {
implementation 'com.github.pme123:play-akka-telegrambot4s:0.1.0'
}
dependencies {
implementation("com.github.pme123:play-akka-telegrambot4s:0.1.0")
}
<dependency>
<groupId>com.github.pme123</groupId>
<artifactId>play-akka-telegrambot4s</artifactId>
<version>0.1.0</version>
</dependency>
libraryDependencies += "com.github.pme123" % "play-akka-telegrambot4s" % "0.1.0"
:dependencies [[com.github.pme123/play-akka-telegrambot4s "0.1.0"]]
Small library to handle multiple conversations with a telegram bot.
After the first steps with Telegram we like to add Conversations and Services in an effective way.
For that we add another layer (library) that should help you with:
Like the name suggests it's supporting only Telegram. The idea is to extend that to other messaging provider at a later time.
Before you start your own project, please check:
It allows you to add the token in the classpath (add pme.bot.token
-file in the conf
-directory).
Or pass it as a system property, like -Dpme.bot.token=[token]
To get an information from the Bot that does not need any interaction with the Bot, you can use a Service (stateless request). Here an example:
/**
* The user sends command /hello
* The Bot answers with hello to the user
*/
case class HelloService()
extends ChatService { // this is a Service
// the standard actor receive method
def receive: Receive = {
// a service only receives the message without any callback data
case Command(msg, _) =>
// return a simple hello with a Bot helper
bot.sendMessage(msg, s"hello ${msg.from.map(_.firstName)}")
case other =>
warn(s"Not expected message: $other")
}
}
Provide a standard constructor for an Actor:
object HelloService {
// the command to listen for
val command = "/hello"
// constructor of the Service - which is an Actor
def props: Props = Props(HelloService())
}
And finally subscribe the Service:
// a singleton will inject all needed dependencies and subscribe the service
@Singleton
class HelloServiceSubscription @Inject()(@Named("commandDispatcher")
val commandDispatcher: ActorRef
, val system: ActorSystem) {
import HelloService._
// subscribe the HelloService to the CommandDispatcher
commandDispatcher ! Subscription(command, SubscrService
, Some(_ => system.actorOf(props)))
}
Here is the complete example: HelloService
That's what you actually understand as a chat. A stateful conversation where the Bot guides the user through a defined process (workflow).
For a conversation we use a Finite State Machine from Akka: akka.actor.FSM
which is also an Actor.
Here a simple conversation: CounterConversation
Here the main difference is that we now have a state that must be handled:
when(Counting) { // when the state is Counting, this function is called
// FSM returns an Event that contains:
// - the Command from the CommandDispatcher
// - the State from the last step (using Count(n))
case Event(Command(msg, _), Count(n)) =>
val count = n + 1
requestCount += 1
// send the updated button using the BotFacade
bot.sendEditMessage(msg, countButton(count))
// this is a simple conversation that stays always in the same state.
// pass the State to the next step
stay() using Count(count)
}
Check out the Akka Documentation
A more sophisticated example you find here: Telegram Bot with Play Framework, Akka FSM, Scala.js, Binding.scala
The library also supports doing your work asynchronously. An extra state is needed. The example is the taken from my Incident-example:
// the process is asynchronous (getFilePath returns a Future)
bot.getFilePath(msg).map {
case Some((fileId, path)) =>
// standard response to the user (immediate response)
bot.sendMessage(msg, "Ok, just add another Photo.")
// async: the result is send to itself (ChatConversation) - the uploaded photo is added to the state.
self ! ExecutionResult(AddAdditionalInfo, incidentData.copy(assets = Asset(fileId, path) :: incidentData.assets))
case _ =>
// in any other case try to bring the user back on track
bot.sendMessage(msg, "You can only add a Photo.")
// async: the result is send to itself (ChatConversation) - no state change.
self ! ExecutionResult(AddAdditionalInfo, incidentData)
}
// async: go to the special step (ChatConversation) - which waits until it gets the ExecutionResult
goto(WaitingForExecution)
So here is the WaitingForExecution:
when(WaitingForExecution) {
case Event(ExecutionResult(state, data), _) =>
goto(state) using data
case Event(Stay, _) =>
stay()
}
As you can see all it does is forwarding the defined state and data received by the ExecutionResult. For more info: Stackoverflow
This allows you to add functionality to all conversation.
There is a RunAspect
included that returns the actual data
from the conversation. This is helpful during development.
Here is an example:
The BotFacade helps you with the reply for the Bot. As mentioned above, for now this is Telegram specific.
To activate a Service or a Conversation you need these 2 steps:
You have to add the following to Module.configure()
// the generic CommandDispatcher
bindActor[CommandDispatcher]("commandDispatcher")
// starts the Bot itself (Boundary)
bind(classOf[BotRunner]).asEagerSingleton()
// your Services:
bind(classOf[HelloServiceSubscription]).asEagerSingleton()
// your Conversations:
bind(classOf[CounterServiceSubscription]).asEagerSingleton()
// your RunAspects
bind(classOf[LogStateSubscription]).asEagerSingleton()
Set the commands with the BotFather
/setcommands
, e.g:
hello - Simple Hello World.
counter - Counts the time a User hits the button.
logstate - This prints the content you have created and not persisted.
Add resolver:
resolvers += "jitpack" at "https://jitpack.io"
Add dependency:
libraryDependencies += "com.github.pme123" % "play-akka-telegrambot4s" % "0.0.5"
Check for the latest version on Jitpack.io