From afe113c2db1c0b79250f76faea90c34b0b5da3f4 Mon Sep 17 00:00:00 2001 From: Pavel Kachalouski Date: Sat, 18 May 2019 01:37:37 +0200 Subject: [PATCH] Dockerized telegram bot --- build.sbt | 11 +++-- project/Dependencies.scala | 34 +++++++++----- project/build.properties | 2 +- .../main/resources/application.conf | 2 +- .../src => src}/main/resources/logback.xml | 2 +- .../main/resources/telegram-bot.p12 | Bin .../main/resources/telegram-bot.pem | 0 .../main/scala/eu/xeppaka/bot/BotUri.scala | 0 .../eu/xeppaka/bot/CheckDeliveryDialog.scala | 0 .../xeppaka/bot/CzechPostDeliveryCheck.scala | 0 .../scala/eu/xeppaka/bot/DialogManager.scala | 4 +- src/main/scala/eu/xeppaka/bot/Main.scala | 44 ++++++++++++++++++ .../main/scala/eu/xeppaka/bot/PostType.scala | 0 .../scala/eu/xeppaka/bot/TelegramBot.scala | 29 +++++++----- .../eu/xeppaka/bot/TelegramEntities.scala | 0 .../bot/TelegramEntitiesDerivations.scala | 0 .../src/main/scala/eu/xeppaka/bot/Main.scala | 44 ------------------ 17 files changed, 97 insertions(+), 75 deletions(-) rename {telegram-bot/src => src}/main/resources/application.conf (95%) rename {telegram-bot/src => src}/main/resources/logback.xml (92%) rename {telegram-bot/src => src}/main/resources/telegram-bot.p12 (100%) rename {telegram-bot/src => src}/main/resources/telegram-bot.pem (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/BotUri.scala (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/DialogManager.scala (95%) create mode 100644 src/main/scala/eu/xeppaka/bot/Main.scala rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/PostType.scala (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/TelegramBot.scala (90%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/TelegramEntities.scala (100%) rename {telegram-bot/src => src}/main/scala/eu/xeppaka/bot/TelegramEntitiesDerivations.scala (100%) delete mode 100644 telegram-bot/src/main/scala/eu/xeppaka/bot/Main.scala diff --git a/build.sbt b/build.sbt index 8422276..246d6a5 100644 --- a/build.sbt +++ b/build.sbt @@ -3,14 +3,15 @@ import Dependencies._ lazy val commonSettings = Seq( organization := "com.example", scalaVersion := "2.12.8", - version := "0.1.0-SNAPSHOT", + version := "1.0.0", mainClass := Some("eu.xeppaka.bot.Main") ) inThisBuild(commonSettings) -lazy val `telegram-bot` = (project in file("telegram-bot")) +lazy val `telegram-bot-delivery` = (project in file(".")) .settings( + name := "telegram-bot-delivery", libraryDependencies ++= Seq( scalaTest % Test, akka, @@ -23,6 +24,10 @@ lazy val `telegram-bot` = (project in file("telegram-bot")) circleGeneric, circleParser, circeAkkaHttp - ) + ), + Docker / defaultLinuxInstallLocation := "/opt/telegram-bot-delivery", + Docker / dockerExposedPorts := Seq(8443), + Docker / dockerRepository := Some("registry.xeppaka.eu:443") ) + .enablePlugins(JavaServerAppPackaging) .enablePlugins(DockerPlugin) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index cc65768..c0cc943 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,16 +1,26 @@ import sbt._ +import Dependencies.Versions._ + object Dependencies { - lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.19" - lazy val akkaTyped = "com.typesafe.akka" %% "akka-actor-typed" % "2.5.19" - lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % "2.5.19" - lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.5" - lazy val akkaPersistence = "com.typesafe.akka" %% "akka-persistence-typed" % "2.5.19" - lazy val levelDbJni = "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8" - //lazy val vkapi = "com.vk.api" % "sdk" % "0.5.12" - lazy val circleCore = "io.circe" %% "circe-core" % "0.10.0" - lazy val circleGeneric = "io.circe" %% "circe-generic" % "0.10.0" - lazy val circleParser = "io.circe" %% "circe-parser" % "0.10.0" - lazy val circeAkkaHttp = "de.heikoseeberger" %% "akka-http-circe" % "1.22.0" - lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5" + object Versions { + val akkaVersion = "2.5.22" + val akkaHttpVersion = "10.1.8" + val levelDbJniVersion = "1.8" + val circeVersion = "0.11.1" + val akkaHttpCirceVersion = "1.23.0" + val scalaTestVersion = "3.0.5" + } + + lazy val akka = "com.typesafe.akka" %% "akka-actor" % akkaVersion + lazy val akkaTyped = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion + lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % akkaVersion + lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % akkaHttpVersion + lazy val akkaPersistence = "com.typesafe.akka" %% "akka-persistence-typed" % akkaVersion + lazy val levelDbJni = "org.fusesource.leveldbjni" % "leveldbjni-all" % levelDbJniVersion + lazy val circleCore = "io.circe" %% "circe-core" % circeVersion + lazy val circleGeneric = "io.circe" %% "circe-generic" % circeVersion + lazy val circleParser = "io.circe" %% "circe-parser" % circeVersion + lazy val circeAkkaHttp = "de.heikoseeberger" %% "akka-http-circe" % akkaHttpCirceVersion + lazy val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion } diff --git a/project/build.properties b/project/build.properties index 72f9028..c0bab04 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.7 +sbt.version=1.2.8 diff --git a/telegram-bot/src/main/resources/application.conf b/src/main/resources/application.conf similarity index 95% rename from telegram-bot/src/main/resources/application.conf rename to src/main/resources/application.conf index 426474b..2fd03d3 100644 --- a/telegram-bot/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1,5 +1,5 @@ akka { - loglevel = "DEBUG" + loglevel = "INFO" extensions = [akka.persistence.Persistence] diff --git a/telegram-bot/src/main/resources/logback.xml b/src/main/resources/logback.xml similarity index 92% rename from telegram-bot/src/main/resources/logback.xml rename to src/main/resources/logback.xml index ab73bdc..7cd947a 100644 --- a/telegram-bot/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -5,7 +5,7 @@ - + diff --git a/telegram-bot/src/main/resources/telegram-bot.p12 b/src/main/resources/telegram-bot.p12 similarity index 100% rename from telegram-bot/src/main/resources/telegram-bot.p12 rename to src/main/resources/telegram-bot.p12 diff --git a/telegram-bot/src/main/resources/telegram-bot.pem b/src/main/resources/telegram-bot.pem similarity index 100% rename from telegram-bot/src/main/resources/telegram-bot.pem rename to src/main/resources/telegram-bot.pem diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/BotUri.scala b/src/main/scala/eu/xeppaka/bot/BotUri.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/BotUri.scala rename to src/main/scala/eu/xeppaka/bot/BotUri.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala b/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala rename to src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala b/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala rename to src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/DialogManager.scala b/src/main/scala/eu/xeppaka/bot/DialogManager.scala similarity index 95% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/DialogManager.scala rename to src/main/scala/eu/xeppaka/bot/DialogManager.scala index d21eeaa..65e7f32 100644 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/DialogManager.scala +++ b/src/main/scala/eu/xeppaka/bot/DialogManager.scala @@ -4,7 +4,7 @@ import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import akka.persistence.typed.PersistenceId import akka.persistence.typed.scaladsl.EventSourcedBehavior.{CommandHandler, EventHandler} -import akka.persistence.typed.scaladsl.{Effect, EventSourcedBehavior} +import akka.persistence.typed.scaladsl.{Effect, EffectBuilder, EventSourcedBehavior} import akka.util.Timeout import eu.xeppaka.bot.CheckDeliveryDialog.{ProcessMessageFailure, ProcessMessageSuccess} import eu.xeppaka.bot.TelegramEntities.Update @@ -36,7 +36,7 @@ object DialogManager { if (update.message.isDefined) { val chatId = update.message.get.chat.id - val effect: Effect[Event, State] = if (state.dialogs.contains(chatId)) { + val effect: EffectBuilder[Event, State] = if (state.dialogs.contains(chatId)) { Effect.none } else { Effect.persist(DialogAdded(chatId)) diff --git a/src/main/scala/eu/xeppaka/bot/Main.scala b/src/main/scala/eu/xeppaka/bot/Main.scala new file mode 100644 index 0000000..b236d51 --- /dev/null +++ b/src/main/scala/eu/xeppaka/bot/Main.scala @@ -0,0 +1,44 @@ +package eu.xeppaka.bot + +import java.nio.file.Paths + +import akka.actor.Scheduler +import akka.actor.typed.scaladsl.AskPattern._ +import akka.actor.typed.scaladsl.Behaviors +import akka.actor.typed.scaladsl.adapter._ +import akka.actor.typed.{ActorSystem, DispatcherSelector, SupervisorStrategy} +import akka.http.scaladsl.Http +import akka.util.Timeout +import akka.{Done, actor} + +import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContextExecutor, Future} +import scala.io.StdIn + +object Main { + def main(args: Array[String]): Unit = { + val botId = System.getProperty("botId", "570855144:AAEv7b817cuq2JJI9f2kG5B9G3zW1x-btz4") + val localPort = 8443 + val hookDomain = System.getProperty("hookDomain", "xeppaka.eu") + val hookPort = System.getProperty("hookPort", "8443").toInt + val useHttpsServer = System.getProperty("useHttpsServer", "true").toBoolean + + val botBehavior = Behaviors.supervise(TelegramBot.behavior(botId, "0.0.0.0", localPort, hookDomain, hookPort, useHttpsServer)).onFailure(SupervisorStrategy.restart) + val telegramBot = ActorSystem(botBehavior, "telegram-bot-delivery") +// implicit val actorSystem: actor.ActorSystem = telegramBot.toUntyped +// implicit val executionContext: ExecutionContextExecutor = telegramBot.dispatchers.lookup(DispatcherSelector.default()) +// implicit val scheduler: Scheduler = telegramBot.scheduler +// implicit val timeout: Timeout = 10.seconds + +// println("Press enter to finish bot...") +// StdIn.readLine() +// +// val stopFuture: Future[Done] = telegramBot ? (ref => TelegramBot.Stop(ref)) +// +// val terminateFuture = stopFuture +// .andThen { case _ => Http().shutdownAllConnectionPools() } +// .andThen { case _ => telegramBot.terminate() } +// +// Await.ready(terminateFuture, 20.seconds) + } +} diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/PostType.scala b/src/main/scala/eu/xeppaka/bot/PostType.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/PostType.scala rename to src/main/scala/eu/xeppaka/bot/PostType.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala b/src/main/scala/eu/xeppaka/bot/TelegramBot.scala similarity index 90% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala rename to src/main/scala/eu/xeppaka/bot/TelegramBot.scala index 94a76b3..7b37c5c 100644 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala +++ b/src/main/scala/eu/xeppaka/bot/TelegramBot.scala @@ -1,6 +1,7 @@ package eu.xeppaka.bot import java.io.InputStream +import java.nio.file.Path import java.security.{KeyStore, SecureRandom} import java.util.UUID @@ -20,7 +21,7 @@ import eu.xeppaka.bot.TelegramEntities._ import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory} import scala.collection.immutable -import scala.concurrent.ExecutionContextExecutor +import scala.concurrent.{ExecutionContextExecutor, Future} import scala.concurrent.duration._ import scala.io.Source import scala.util.{Failure, Success} @@ -34,7 +35,7 @@ object TelegramBot { case object GetBotInfo case object GetWebhookInfo - def behavior(botId: String, interface: String, localPort: Int, hookPort: Int): Behavior[Command] = Behaviors.setup[Command] { ctx => + def behavior(botId: String, interface: String, localPort: Int, hookDomain: String, hookPort: Int, useHttpsServer: Boolean = true): Behavior[Command] = Behaviors.setup[Command] { ctx => ctx.log.info("action=start_bot") implicit val untypedSystem: ActorSystem = ctx.system.toUntyped @@ -44,8 +45,8 @@ object TelegramBot { val botUri = BotUri(botId) val http: HttpExt = Http() val hookId = UUID.randomUUID().toString - val webhookUri = Uri(s"https://xeppaka.eu:$hookPort/$hookId") - val httpsContext = createHttpsConnectionContext + val webhookUri = Uri(s"https://$hookDomain:$hookPort/$hookId") + val httpsContext = if (useHttpsServer) Some(createHttpsConnectionContext) else None val stashBuffer = StashBuffer[Command](10) val dialogManager = ctx.spawnAnonymous(Behaviors.supervise(DialogManager.behavior(botUri)).onFailure(SupervisorStrategy.restart)) val routes = botRoutes(hookId, dialogManager)(untypedSystem.scheduler) @@ -57,7 +58,7 @@ object TelegramBot { ctx.log.info("action=bind_server interface={} port={}", interface, localPort) http - .bindAndHandle(routes, interface, localPort, httpsContext) + .bindAndHandle(routes, interface, localPort, httpsContext.getOrElse(http.defaultServerHttpContext)) .onComplete { case Success(binding) => ctx.self ! BindingSuccess(binding) case Failure(exception) => ctx.self ! BindingFailure(exception) @@ -112,15 +113,21 @@ object TelegramBot { implicit val executionContextExecutor: ExecutionContextExecutor = ctx.system.dispatchers.lookup(DispatcherSelector.default()) val urlEntity = HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, ByteString(webhookUri.toString())) - val urlPart = Multipart.FormData.BodyPart.Strict("url", urlEntity) + val urlPart = Some(Multipart.FormData.BodyPart.Strict("url", urlEntity)) - val certificate = ByteString(Source.fromResource("telegram-bot.pem").mkString) - val certificateEntity = HttpEntity.Strict(ContentTypes.`application/octet-stream`, certificate) - val certificatePart = Multipart.FormData.BodyPart.Strict("certificate", certificateEntity, Map("filename" -> "telegram-bot.pem")) + val certificatePart = if (useHttpsServer) { + val certificate = ByteString(Source.fromResource("telegram-bot.pem").mkString) + val certificateEntity = HttpEntity.Strict(ContentTypes.`application/octet-stream`, certificate) - val setWebhookFormData = Multipart.FormData.Strict(immutable.Seq(urlPart, certificatePart)) + Some(Multipart.FormData.BodyPart.Strict("certificate", certificateEntity, Map("filename" -> "cert.pem"))) + } else { + None + } - Marshal(setWebhookFormData) + val formParts = immutable.Seq(urlPart, certificatePart).flatten + val formData = Multipart.FormData.Strict(formParts) + + Marshal(formData) .to[RequestEntity] .flatMap(requestEntity => http.singleRequest(HttpRequest(uri = botUri.setWebhook, method = HttpMethods.POST, entity = requestEntity))) .onComplete { diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramEntities.scala b/src/main/scala/eu/xeppaka/bot/TelegramEntities.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramEntities.scala rename to src/main/scala/eu/xeppaka/bot/TelegramEntities.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramEntitiesDerivations.scala b/src/main/scala/eu/xeppaka/bot/TelegramEntitiesDerivations.scala similarity index 100% rename from telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramEntitiesDerivations.scala rename to src/main/scala/eu/xeppaka/bot/TelegramEntitiesDerivations.scala diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/Main.scala b/telegram-bot/src/main/scala/eu/xeppaka/bot/Main.scala deleted file mode 100644 index 142f535..0000000 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/Main.scala +++ /dev/null @@ -1,44 +0,0 @@ -package eu.xeppaka.bot - -import akka.actor.Scheduler -import akka.actor.typed.scaladsl.AskPattern._ -import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.scaladsl.adapter._ -import akka.actor.typed.{ActorSystem, DispatcherSelector, SupervisorStrategy} -import akka.http.scaladsl.Http -import akka.util.Timeout -import akka.{Done, actor} - -import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContextExecutor, Future} -import scala.io.StdIn - -object Main { - def main(args: Array[String]): Unit = { - val isProduction = System.getProperty("isProduction", "false").toBoolean - - val (botId, localPort, hookPort) = if (isProduction) { - ("693134480:AAE8JRXA6j1mkOKTaxapP6A-E4LPHRuiIf8", 88, 88) // delivery bot - } else { - ("570855144:AAEv7b817cuq2JJI9f2kG5B9G3zW1x-btz4", 8443, 8443) // useless bot - } - - val botBehavior = Behaviors.supervise(TelegramBot.behavior(botId, "0.0.0.0", localPort, hookPort)).onFailure(SupervisorStrategy.restart) - val telegramBot = ActorSystem(botBehavior, "telegram-bot") - implicit val actorSystem: actor.ActorSystem = telegramBot.toUntyped - implicit val executionContext: ExecutionContextExecutor = telegramBot.dispatchers.lookup(DispatcherSelector.default()) - implicit val scheduler: Scheduler = telegramBot.scheduler - implicit val timeout: Timeout = 10.seconds - - println("Press enter to finish bot...") - StdIn.readLine() - - val stopFuture: Future[Done] = telegramBot ? (ref => TelegramBot.Stop(ref)) - - val terminateFuture = stopFuture - .andThen { case _ => Http().shutdownAllConnectionPools() } - .andThen { case _ => telegramBot.terminate() } - - Await.ready(terminateFuture, 20.seconds) - } -}