176 lines
6.7 KiB
Scala
176 lines
6.7 KiB
Scala
package eu.xeppaka.bot1
|
|
|
|
import java.io.InputStream
|
|
import java.security.{KeyStore, SecureRandom}
|
|
import java.util.UUID
|
|
|
|
import akka.actor
|
|
import akka.actor.Scheduler
|
|
import akka.actor.typed.scaladsl.adapter._
|
|
import akka.actor.typed.{ActorSystem, DispatcherSelector}
|
|
import akka.http.scaladsl.marshalling.Marshal
|
|
import akka.http.scaladsl.model._
|
|
import akka.http.scaladsl.server.Directives._
|
|
import akka.http.scaladsl.server.{Route, RouteResult}
|
|
import akka.http.scaladsl.unmarshalling.Unmarshal
|
|
import akka.http.scaladsl.{ConnectionContext, Http, HttpExt, HttpsConnectionContext}
|
|
import akka.stream.ActorMaterializer
|
|
import akka.util.{ByteString, Timeout}
|
|
import eu.xeppaka.bot1.actors.UpdateActor
|
|
import eu.xeppaka.bot1.actors.UpdateActor.UpdateResponse
|
|
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
|
|
|
|
import scala.collection.immutable
|
|
import scala.concurrent.duration._
|
|
import scala.concurrent.{ExecutionContextExecutor, Future}
|
|
import scala.io.{Source, StdIn}
|
|
import scala.util.{Failure, Success}
|
|
|
|
class TelegramBotServer(botId: String, port: Int, httpsContext: Option[HttpsConnectionContext]) {
|
|
|
|
import FailFastCirceSupport._
|
|
import eu.xeppaka.bot1.TelegramEntities._
|
|
import io.circe.generic.auto._
|
|
|
|
private val botUri = BotUri(botId)
|
|
private implicit val updateSystem: ActorSystem[UpdateActor.UpdateCommand] = ActorSystem(UpdateActor.behavior, "telegram-bot")
|
|
private implicit val actorSystem: actor.ActorSystem = updateSystem.toUntyped
|
|
private implicit val materializer: ActorMaterializer = ActorMaterializer()
|
|
private implicit val executionContext: ExecutionContextExecutor = updateSystem.dispatchers.lookup(DispatcherSelector.default())
|
|
|
|
private val http: HttpExt = Http()
|
|
private val hookId = UUID.randomUUID().toString
|
|
private val webhookUri = Uri(s"https://xeppaka.eu:$port/$hookId")
|
|
private val bindingFuture = http.bindAndHandle(botRoutes(hookId),
|
|
"pkcloud",
|
|
port,
|
|
connectionContext = httpsContext.getOrElse(http.defaultClientHttpsContext))
|
|
|
|
println(s"Webhook path: $webhookUri")
|
|
setWebhook()
|
|
|
|
def stop(): Unit = {
|
|
bindingFuture
|
|
.flatMap(binding => deleteWebhook().map(_ => binding))
|
|
.flatMap(binding => http.shutdownAllConnectionPools().map(_ => binding))
|
|
.flatMap(binding => binding.unbind())
|
|
.onComplete(_ => updateSystem.terminate())
|
|
}
|
|
|
|
def printRequestMethodAndResponseStatus(req: HttpRequest)(res: RouteResult): Unit = {
|
|
println(req)
|
|
println(res)
|
|
}
|
|
|
|
def botRoutes(hookId: String): Route = {
|
|
path(hookId) {
|
|
post {
|
|
entity(as[Update]) { update =>
|
|
handleWith(receivedUpdate)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private def receivedUpdate(update: Update): Future[HttpResponse] = {
|
|
import akka.actor.typed.scaladsl.AskPattern._
|
|
|
|
implicit val timeout: Timeout = 3.seconds
|
|
implicit val scheduler: Scheduler = updateSystem.scheduler
|
|
|
|
val result: Future[UpdateActor.UpdateResponse] = updateSystem ? (ref => UpdateActor.UpdateReceived(update, ref))
|
|
|
|
result.andThen {
|
|
case Success(response) => sendResponse(response.chatId, response.text)
|
|
case Failure(ex) => println("Failed to process message...")
|
|
}
|
|
|
|
result.map(res => HttpResponse()).fallbackTo(Future.successful(HttpResponse()))
|
|
}
|
|
|
|
private def sendResponse(chatId: Long, text: String) = {
|
|
import io.circe._, io.circe.generic.auto._, io.circe.syntax._
|
|
|
|
val sendMessage = SendMessage(chatId, text)
|
|
val printer = Printer.noSpaces.copy(dropNullValues = true)
|
|
val json = printer.pretty(sendMessage.asJson)
|
|
val request = HttpRequest(HttpMethods.POST, uri = botUri.sendMessage, entity = HttpEntity.Strict(ContentTypes.`application/json`, ByteString(json)))
|
|
http.singleRequest(request)
|
|
}
|
|
|
|
def getBotInfo: Future[Response[GetMe]] = {
|
|
http.singleRequest(HttpRequest(uri = botUri.getMe)).flatMap(Unmarshal(_).to[Response[GetMe]])
|
|
}
|
|
|
|
private def setWebhook(): Future[HttpResponse] = {
|
|
print("Setting webhook...")
|
|
val urlEntity = HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, ByteString(webhookUri.toString()))
|
|
val urlPart = 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 setWebhookFormData = Multipart.FormData.Strict(immutable.Seq(urlPart, certificatePart))
|
|
|
|
Marshal(setWebhookFormData)
|
|
.to[RequestEntity]
|
|
.flatMap(requestEntity => http.singleRequest(HttpRequest(uri = botUri.setWebhook, method = HttpMethods.POST, entity = requestEntity)))
|
|
.andThen {
|
|
case Success(response) => println(s" ${response.status.value}")
|
|
case Failure(exception) => println(s" failed with exception: ${exception.getMessage}")
|
|
}
|
|
}
|
|
|
|
private def deleteWebhook(): Future[HttpResponse] = {
|
|
print("Deleting webhook...")
|
|
http
|
|
.singleRequest(HttpRequest(uri = botUri.deleteWebhook, method = HttpMethods.POST))
|
|
.andThen {
|
|
case Success(response) => println(s" ${response.status.value}")
|
|
case Failure(exception) => println(s" failed with exception: ${exception.getMessage}")
|
|
}
|
|
}
|
|
|
|
def getWebhookInfo(): Future[Response[WebhookInfo]] = {
|
|
http
|
|
.singleRequest(HttpRequest(uri = botUri.getWebhookInfo, method = HttpMethods.GET))
|
|
.flatMap(Unmarshal(_).to[Response[WebhookInfo]])
|
|
}
|
|
}
|
|
|
|
object TelegramBotServer {
|
|
private val botId = "570855144:AAEv7b817cuq2JJI9f2kG5B9G3zW1x-btz4"
|
|
|
|
def apply(port: Int, httpsContext: Option[HttpsConnectionContext]): TelegramBotServer = new TelegramBotServer(botId, port, httpsContext)
|
|
|
|
def main(args: Array[String]): Unit = {
|
|
val httpsContext = createHttpsConnectionContext
|
|
val tbs = TelegramBotServer(88, Some(createHttpsConnectionContext))
|
|
|
|
StdIn.readLine()
|
|
tbs.stop()
|
|
}
|
|
|
|
def createHttpsConnectionContext: HttpsConnectionContext = {
|
|
val password: Array[Char] = "".toCharArray // do not store passwords in code, read them from somewhere safe!
|
|
|
|
val ks: KeyStore = KeyStore.getInstance("PKCS12")
|
|
val keystore: InputStream = getClass.getResourceAsStream("/telegram-bot.p12")
|
|
|
|
require(keystore != null, "Keystore required!")
|
|
ks.load(keystore, password)
|
|
|
|
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
|
|
keyManagerFactory.init(ks, password)
|
|
|
|
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
|
|
tmf.init(ks)
|
|
|
|
val sslContext: SSLContext = SSLContext.getInstance("TLS")
|
|
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
|
|
|
|
ConnectionContext.https(sslContext)
|
|
}
|
|
}
|