Added menu for selecting parcel for remove. Parcel date parsing.
This commit is contained in:
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
||||||
67
.idea/modules/telegram-bot.iml
generated
67
.idea/modules/telegram-bot.iml
generated
@@ -6,6 +6,7 @@
|
|||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$/../../telegram-bot">
|
<content url="file://$MODULE_DIR$/../../telegram-bot">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/src/main/scala" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/src/main/scala" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/src/test/scala" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/target/scala-2.12/src_managed/main" isTestSource="false" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/target/scala-2.12/src_managed/main" isTestSource="false" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/target/scala-2.12/src_managed/test" isTestSource="true" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/target/scala-2.12/src_managed/test" isTestSource="true" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/src/main/resources" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/../../telegram-bot/src/main/resources" type="java-resource" />
|
||||||
@@ -14,39 +15,39 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="sbt: com.chuusai:shapeless_2.12:2.3.3:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.chuusai:shapeless_2.12:2.3.3:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.fusesource.leveldbjni:leveldbjni-all:1.8:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang:scala-reflect:2.12.8:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: de.heikoseeberger:akka-http-circe_2.12:1.22.0:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.8:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.typelevel:machinist_2.12:0.6.5:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.typesafe:ssl-config-core_2.12:0.3.6:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.typelevel:cats-macros_2.12:1.4.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.typelevel:cats-kernel_2.12:1.4.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.typelevel:cats-core_2.12:1.4.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.spire-math:jawn-parser_2.12:0.13.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-parser-combinators_2.12:1.1.1:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: io.circe:circe-parser_2.12:0.10.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: io.circe:circe-numbers_2.12:0.10.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: io.circe:circe-jawn_2.12:0.10.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: io.circe:circe-generic_2.12:0.10.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: io.circe:circe-core_2.12:0.10.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-stream_2.12:2.5.17:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-protobuf_2.12:2.5.17:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-parsing_2.12:10.1.5:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-http_2.12:10.1.5:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-http-core_2.12:10.1.5:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.12:2.5.17:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor-typed_2.12:2.5.17:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe:ssl-config-core_2.12:0.2.4:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe:config:1.3.3:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.reactivestreams:reactive-streams:1.0.2:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-java8-compat_2.12:0.8.0:jar" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="sbt: org.scala-lang.modules:scala-xml_2.12:1.0.6:jar" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="sbt: org.scalactic:scalactic_2.12:3.0.5:jar" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest_2.12:3.0.5:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.typelevel:macro-compat_2.12:1.1.1:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.typelevel:macro-compat_2.12:1.1.1:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.7:jar" level="project" />
|
<orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest_2.12:3.0.5:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang:scala-reflect:2.12.7:jar" level="project" />
|
<orderEntry type="library" scope="TEST" name="sbt: org.scalactic:scalactic_2.12:3.0.5:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor-testkit-typed_2.12:2.5.17:jar" level="project" />
|
<orderEntry type="library" scope="TEST" name="sbt: org.scala-lang.modules:scala-xml_2.12:1.0.6:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-persistence-typed_2.12:2.5.17:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-java8-compat_2.12:0.8.0:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-persistence_2.12:2.5.17:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.reactivestreams:reactive-streams:1.0.2:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-testkit_2.12:2.5.17:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.typesafe:config:1.3.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-http-core_2.12:10.1.5:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-http_2.12:10.1.5:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-parsing_2.12:10.1.5:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-core_2.12:0.10.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-generic_2.12:0.10.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-jawn_2.12:0.10.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-numbers_2.12:0.10.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-parser_2.12:0.10.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-parser-combinators_2.12:1.1.1:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.spire-math:jawn-parser_2.12:0.13.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-core_2.12:1.4.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-kernel_2.12:1.4.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-macros_2.12:1.4.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:machinist_2.12:0.6.5:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: de.heikoseeberger:akka-http-circe_2.12:1.22.0:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.fusesource.leveldbjni:leveldbjni-all:1.8:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor-testkit-typed_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor-typed_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-persistence-typed_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-persistence_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-protobuf_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-stream_2.12:2.5.19:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-testkit_2.12:2.5.19:jar" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
2
.idea/modules/telegram-bot1.iml
generated
2
.idea/modules/telegram-bot1.iml
generated
@@ -11,6 +11,6 @@
|
|||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.7:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.8:jar" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -2,7 +2,7 @@ import Dependencies._
|
|||||||
|
|
||||||
lazy val commonSettings = Seq(
|
lazy val commonSettings = Seq(
|
||||||
organization := "com.example",
|
organization := "com.example",
|
||||||
scalaVersion := "2.12.7",
|
scalaVersion := "2.12.8",
|
||||||
version := "0.1.0-SNAPSHOT",
|
version := "0.1.0-SNAPSHOT",
|
||||||
mainClass := Some("eu.xeppaka.bot.Main")
|
mainClass := Some("eu.xeppaka.bot.Main")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import sbt._
|
import sbt._
|
||||||
|
|
||||||
object Dependencies {
|
object Dependencies {
|
||||||
lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.17"
|
lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.19"
|
||||||
lazy val akkaTyped = "com.typesafe.akka" %% "akka-actor-typed" % "2.5.17"
|
lazy val akkaTyped = "com.typesafe.akka" %% "akka-actor-typed" % "2.5.19"
|
||||||
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % "2.5.17"
|
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % "2.5.19"
|
||||||
lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.5"
|
lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.5"
|
||||||
lazy val akkaPersistence = "com.typesafe.akka" %% "akka-persistence-typed" % "2.5.17"
|
lazy val akkaPersistence = "com.typesafe.akka" %% "akka-persistence-typed" % "2.5.19"
|
||||||
lazy val levelDbJni = "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8"
|
lazy val levelDbJni = "org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8"
|
||||||
//lazy val vkapi = "com.vk.api" % "sdk" % "0.5.12"
|
//lazy val vkapi = "com.vk.api" % "sdk" % "0.5.12"
|
||||||
lazy val circleCore = "io.circe" %% "circe-core" % "0.10.0"
|
lazy val circleCore = "io.circe" %% "circe-core" % "0.10.0"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=1.2.6
|
sbt.version=1.2.7
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package eu.xeppaka.bot
|
|||||||
|
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
import akka.actor.typed.scaladsl.{Behaviors, StashBuffer}
|
import akka.actor.typed.scaladsl.{Behaviors, StashBuffer}
|
||||||
import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector}
|
import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector, SupervisorStrategy}
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.Http
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import akka.util.{ByteString, Timeout}
|
import akka.util.{ByteString, Timeout}
|
||||||
import eu.xeppaka.bot.TelegramEntities.{Message, SendMessage}
|
import eu.xeppaka.bot.TelegramEntities._
|
||||||
|
import eu.xeppaka.bot.TelegramEntitiesDerivations._
|
||||||
|
import io.circe.Printer
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
@@ -27,46 +29,63 @@ object CheckDeliveryDialog {
|
|||||||
case object Help extends DialogCommand
|
case object Help extends DialogCommand
|
||||||
|
|
||||||
object DialogCommand {
|
object DialogCommand {
|
||||||
def apply(msg: String, replyTo: ActorRef[CommandResult]): Option[DialogCommand] = msg match {
|
def parse(text: String): DialogCommand = text match {
|
||||||
case "/add" => Some(AddParcel)
|
case "/add" => AddParcel
|
||||||
case "/remove" => Some(RemoveParcel)
|
case "/remove" => RemoveParcel
|
||||||
case "/list" => Some(ListParcels)
|
case "/list" => ListParcels
|
||||||
case "/help" => Some(Help)
|
case "/help" => Help
|
||||||
case _ => None
|
case "/start" => Help
|
||||||
|
case _ => Help
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// json printer
|
||||||
|
private val printer = Printer.noSpaces.copy(dropNullValues = true)
|
||||||
// internal messages
|
// internal messages
|
||||||
private case class DeliveryStateChanged(state: String) extends Command
|
private case class DeliveryStateChanged(state: String) extends Command
|
||||||
|
private val helpMessage =
|
||||||
|
"""
|
||||||
|
|Supported commands:
|
||||||
|
|/add - add parcel to a list of watched parcels
|
||||||
|
|/list - list watched parcels
|
||||||
|
|/remove - remove parcel from a watching list
|
||||||
|
""".stripMargin
|
||||||
|
private val replyKeyboardRemoveMarkup = Some(ReplyKeyboardRemove())
|
||||||
|
|
||||||
def behavior(chatId: Long, botUri: BotUri): Behavior[Command] = Behaviors.setup[Command] { ctx =>
|
def behavior(chatId: Long, botUri: BotUri): Behavior[Command] = Behaviors.setup[Command] { ctx =>
|
||||||
implicit val executionContext: ExecutionContext = ctx.system.dispatchers.lookup(DispatcherSelector.default())
|
implicit val executionContext: ExecutionContext = ctx.system.dispatchers.lookup(DispatcherSelector.default())
|
||||||
val http = Http()(ctx.system.toUntyped)
|
val http = Http()(ctx.system.toUntyped)
|
||||||
val stashBuffer = StashBuffer[Command](100)
|
val stashBuffer = StashBuffer[Command](100)
|
||||||
val deliveryStateAdapter: ActorRef[CzechPostDeliveryCheck.DeliveryStateChanged] = ctx.messageAdapter(stateChanged => DeliveryStateChanged(stateChanged.state))
|
val deliveryStateAdapter: ActorRef[CzechPostDeliveryCheck.DeliveryStateChanged] = ctx.messageAdapter(stateChanged => DeliveryStateChanged(stateChanged.state))
|
||||||
val czechPostDeliveryCheck = ctx.spawnAnonymous(CzechPostDeliveryCheck.behavior(chatId.toString, deliveryStateAdapter))
|
val czechPostDeliveryCheck = ctx.spawnAnonymous(Behaviors.supervise(CzechPostDeliveryCheck.behavior(chatId.toString, deliveryStateAdapter)).onFailure(SupervisorStrategy.restart))
|
||||||
|
|
||||||
def initial: Behavior[Command] = Behaviors.receiveMessage {
|
def initial: Behavior[Command] = waitCommand
|
||||||
|
|
||||||
|
def waitCommand: Behavior[Command] = Behaviors.receiveMessage {
|
||||||
case ProcessMessage(msg, replyTo) =>
|
case ProcessMessage(msg, replyTo) =>
|
||||||
val command = DialogCommand(msg.text.getOrElse("unknown message"), replyTo)
|
val command = msg.text.map(text => DialogCommand.parse(text))
|
||||||
replyTo ! ProcessMessageSuccess
|
replyTo ! ProcessMessageSuccess
|
||||||
|
|
||||||
if (command.isDefined) {
|
if (command.isDefined) {
|
||||||
ctx.self ! command.get
|
ctx.self ! command.get
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
} else {
|
} else {
|
||||||
sendMessage("This command is unsupported.", initial, initial)
|
val message = SendMessage(chatId, "This command is unsupported.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
}
|
}
|
||||||
case AddParcel =>
|
case AddParcel =>
|
||||||
sendMessage("Please enter parcel ID.", waitParcelId(parcelId => addParcel(parcelId)), initial)
|
val message = SendMessage(chatId, "Please enter a parcel ID.")
|
||||||
|
sendMessage(message, waitParcelId(parcelId => addParcel(parcelId)), initial)
|
||||||
case RemoveParcel =>
|
case RemoveParcel =>
|
||||||
sendMessage("Please enter parcel ID.", waitParcelId(parcelId => removeParcel(parcelId)), initial)
|
removeParcel(initial, initial)
|
||||||
case ListParcels =>
|
case ListParcels =>
|
||||||
listParcels
|
listParcels
|
||||||
case Help =>
|
case Help =>
|
||||||
sendMessage("Supported commands: /add, /remove, /list, /help", initial, initial)
|
val message = SendMessage(chatId, helpMessage)
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case DeliveryStateChanged(state) =>
|
case DeliveryStateChanged(state) =>
|
||||||
sendMessage(state, initial, initial)
|
val message = SendMessage(chatId, state, Some("Markdown"))
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case _ =>
|
case _ =>
|
||||||
Behaviors.unhandled
|
Behaviors.unhandled
|
||||||
}
|
}
|
||||||
@@ -84,14 +103,17 @@ object CheckDeliveryDialog {
|
|||||||
|
|
||||||
Behaviors.receiveMessage {
|
Behaviors.receiveMessage {
|
||||||
case AddParcelSuccess =>
|
case AddParcelSuccess =>
|
||||||
sendMessage(s"Parcel $parcelId was added to the watch list.", initial, initial)
|
val message = SendMessage(chatId, s"Parcel $parcelId was added to the watch list.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case AddParcelFailure(exception) =>
|
case AddParcelFailure(exception) =>
|
||||||
exception match {
|
exception match {
|
||||||
case CzechPostDeliveryCheck.DuplicateParcelId(_) =>
|
case CzechPostDeliveryCheck.DuplicateParcelId(_) =>
|
||||||
sendMessage(s"Parcel $parcelId is in the watch list already.", initial, initial)
|
val message = SendMessage(chatId, s"Parcel $parcelId is in the watch list already.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case _ =>
|
case _ =>
|
||||||
ctx.log.error(exception, "action=add_parcel result=failure")
|
ctx.log.error(exception, "action=add_parcel result=failure")
|
||||||
sendMessage(s"Adding parcel failed. Please try again.", initial, initial)
|
val message = SendMessage(chatId, s"Adding parcel failed. Please try again.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
}
|
}
|
||||||
case otherMessage =>
|
case otherMessage =>
|
||||||
stashBuffer.stash(otherMessage)
|
stashBuffer.stash(otherMessage)
|
||||||
@@ -100,7 +122,7 @@ object CheckDeliveryDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def listParcels: Behavior[Command] = Behaviors.setup { ctx =>
|
def listParcels: Behavior[Command] = Behaviors.setup { ctx =>
|
||||||
case class ListParcelsSuccess(parcelsList: String) extends Command
|
case class ListParcelsSuccess(parcelsList: Set[String]) extends Command
|
||||||
case class ListParcelsFailure(exception: Throwable) extends Command
|
case class ListParcelsFailure(exception: Throwable) extends Command
|
||||||
implicit val timeout: Timeout = 5.seconds
|
implicit val timeout: Timeout = 5.seconds
|
||||||
|
|
||||||
@@ -111,17 +133,52 @@ object CheckDeliveryDialog {
|
|||||||
|
|
||||||
Behaviors.receiveMessage {
|
Behaviors.receiveMessage {
|
||||||
case ListParcelsSuccess(parcelsList) =>
|
case ListParcelsSuccess(parcelsList) =>
|
||||||
sendMessage(parcelsList, initial, initial)
|
val messageText = "*List of your watched parcels:*\n" + (if (parcelsList.nonEmpty) parcelsList.toSeq.sorted.mkString("\n") else "(empty)")
|
||||||
|
val message = SendMessage(chatId, messageText, Some("Markdown"))
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case ListParcelsFailure(exception) =>
|
case ListParcelsFailure(exception) =>
|
||||||
ctx.log.error(exception, "action=list_parcels result=failure chat_id={}", chatId)
|
ctx.log.error(exception, "action=list_parcels result=failure chat_id={}", chatId)
|
||||||
sendMessage("Failed to get list of the your watched parcels. Please try again later.", initial, initial)
|
val message = SendMessage(chatId, "Failed to get a list of your watched parcels. Please try again later.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case otherMessage =>
|
case otherMessage =>
|
||||||
stashBuffer.stash(otherMessage)
|
stashBuffer.stash(otherMessage)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def removeParcel(parcelId: String): Behavior[Command] = Behaviors.setup { ctx =>
|
def removeParcel(onSuccess: => Behavior[Command], onFailure: => Behavior[Command]): Behavior[Command] =
|
||||||
|
Behaviors.setup { ctx =>
|
||||||
|
case class ListParcelsSuccess(parcelsList: Set[String]) extends Command
|
||||||
|
case class ListParcelsFailure(exception: Throwable) extends Command
|
||||||
|
implicit val timeout: Timeout = 5.seconds
|
||||||
|
|
||||||
|
ctx.ask[CzechPostDeliveryCheck.Command, CzechPostDeliveryCheck.ListParcelsResult](czechPostDeliveryCheck)(ref => CzechPostDeliveryCheck.ListParcels(ref)) {
|
||||||
|
case Success(CzechPostDeliveryCheck.ListParcelsResult(parcelsList)) => ListParcelsSuccess(parcelsList)
|
||||||
|
case Failure(exception) => ListParcelsFailure(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
Behaviors.receiveMessage {
|
||||||
|
case ListParcelsSuccess(parcelsList) =>
|
||||||
|
if (parcelsList.nonEmpty) {
|
||||||
|
val keyboardButtons = parcelsList.toSeq.sorted.grouped(3).map(_.map(id => KeyboardButton(id))).toSeq
|
||||||
|
val markup = ReplyKeyboardMarkup(keyboard = keyboardButtons, resize_keyboard = Some(true))
|
||||||
|
val message = SendMessage(chatId, "Please enter a parcel id to remove.", reply_markup = Some(markup))
|
||||||
|
sendMessage(message, waitParcelId(parcelId => removeParcelId(parcelId)), onFailure)
|
||||||
|
} else {
|
||||||
|
val message = SendMessage(chatId, "You don't have watched parcels. There is nothing to remove.")
|
||||||
|
sendMessage(message, onSuccess, onFailure)
|
||||||
|
}
|
||||||
|
case ListParcelsFailure(exception) =>
|
||||||
|
ctx.log.error(exception, "action=list_parcels result=failure chat_id={}", chatId)
|
||||||
|
val message = SendMessage(chatId, "Failed to get a list of your watched parcels. Please try again later.")
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
|
case otherMessage =>
|
||||||
|
stashBuffer.stash(otherMessage)
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def removeParcelId(parcelId: String): Behavior[Command] = Behaviors.setup { ctx =>
|
||||||
case object RemoveParcelSuccess extends Command
|
case object RemoveParcelSuccess extends Command
|
||||||
case class RemoveParcelFailure(exception: Throwable) extends Command
|
case class RemoveParcelFailure(exception: Throwable) extends Command
|
||||||
implicit val timeout: Timeout = 5.seconds
|
implicit val timeout: Timeout = 5.seconds
|
||||||
@@ -134,14 +191,17 @@ object CheckDeliveryDialog {
|
|||||||
|
|
||||||
Behaviors.receiveMessage {
|
Behaviors.receiveMessage {
|
||||||
case RemoveParcelSuccess =>
|
case RemoveParcelSuccess =>
|
||||||
sendMessage(s"Parcel $parcelId was removed from the watch list.", initial, initial)
|
val message = SendMessage(chatId, s"Parcel $parcelId was removed from the watch list.", reply_markup = replyKeyboardRemoveMarkup)
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case RemoveParcelFailure(exception) =>
|
case RemoveParcelFailure(exception) =>
|
||||||
exception match {
|
exception match {
|
||||||
case CzechPostDeliveryCheck.ParcelIdNotFound(_) =>
|
case CzechPostDeliveryCheck.ParcelIdNotFound(_) =>
|
||||||
sendMessage(s"Parcel $parcelId is not found in the list of the watched parcels.", initial, initial)
|
val message = SendMessage(chatId, s"Parcel $parcelId is not found in the list of the watched parcels.", reply_markup = replyKeyboardRemoveMarkup)
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
case _ =>
|
case _ =>
|
||||||
ctx.log.error(exception, "action=add_parcel result=failure")
|
ctx.log.error(exception, "action=add_parcel result=failure")
|
||||||
sendMessage(s"Remove of the parcel failed. Please try again.", initial, initial)
|
val message = SendMessage(chatId, s"Remove of the parcel failed. Please try again.", reply_markup = replyKeyboardRemoveMarkup)
|
||||||
|
sendMessage(message, initial, initial)
|
||||||
}
|
}
|
||||||
case otherMessage =>
|
case otherMessage =>
|
||||||
stashBuffer.stash(otherMessage)
|
stashBuffer.stash(otherMessage)
|
||||||
@@ -149,52 +209,68 @@ object CheckDeliveryDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def waitParcelId(onSuccess: String => Behavior[Command]): Behavior[Command] = Behaviors.setup[Command] { ctx =>
|
// def selectPostType(onFinish: PostType => Behavior[Command]): Behavior[Command] = Behaviors.receiveMessage {
|
||||||
Behaviors.receiveMessage {
|
//
|
||||||
|
// case ProcessMessage(msg, replyTo) =>
|
||||||
|
// val button1 = KeyboardButton("button1")
|
||||||
|
// val button2 = KeyboardButton("button2")
|
||||||
|
// val keyboard = ReplyKeyboardMarkup(Seq(Seq(button1, button2)))
|
||||||
|
// val message = SendMessage(chatId, "Please enter parcel ID.", reply_markup = Some(keyboard))
|
||||||
|
// sendMessage(message, waitParcelId(parcelId => addParcel(parcelId)), initial)
|
||||||
|
// }
|
||||||
|
|
||||||
|
def waitParcelId(onFinish: String => Behavior[Command]): Behavior[Command] = Behaviors.receiveMessage {
|
||||||
case ProcessMessage(msg, replyTo) =>
|
case ProcessMessage(msg, replyTo) =>
|
||||||
if (msg.text.isDefined) {
|
if (msg.text.isDefined) {
|
||||||
val parcelId = msg.text.get
|
val parcelId = msg.text.get
|
||||||
replyTo ! ProcessMessageSuccess
|
replyTo ! ProcessMessageSuccess
|
||||||
onSuccess(parcelId)
|
onFinish(parcelId)
|
||||||
} else {
|
} else {
|
||||||
replyTo ! ProcessMessageSuccess
|
replyTo ! ProcessMessageSuccess
|
||||||
waitParcelId(onSuccess)
|
waitParcelId(onFinish)
|
||||||
}
|
}
|
||||||
case otherMsg =>
|
case otherMsg =>
|
||||||
stashBuffer.stash(otherMsg)
|
stashBuffer.stash(otherMsg)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def sendMessage(text: String, onSuccess: => Behavior[Command], onFailure: => Behavior[Command]): Behavior[Command] = Behaviors.setup[Command] { ctx =>
|
def sendMessage(message: SendMessage, onSuccess: => Behavior[Command], onFailure: => Behavior[Command], attempt: Int = 0): Behavior[Command] = Behaviors.setup[Command] { ctx =>
|
||||||
import io.circe._
|
|
||||||
import io.circe.generic.auto._
|
import io.circe.generic.auto._
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
|
|
||||||
case object SendMessageSuccess extends Command
|
case object SendMessageSuccess extends Command
|
||||||
case class SendMessageFailure(exception: Throwable) extends Command
|
case class SendMessageFailure(exception: Throwable) extends Command
|
||||||
|
|
||||||
val sendMessage = SendMessage(chatId, text, Some("Markdown"))
|
val json = printer.pretty(message.asJson)
|
||||||
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)))
|
val request = HttpRequest(HttpMethods.POST, uri = botUri.sendMessage, entity = HttpEntity.Strict(ContentTypes.`application/json`, ByteString(json)))
|
||||||
|
|
||||||
|
ctx.log.debug("action=send_message status=started chat_id={} message={}", chatId, json)
|
||||||
|
|
||||||
http
|
http
|
||||||
.singleRequest(request)
|
.singleRequest(request)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(response) => if (response.status.isSuccess()) {
|
case Success(response) => if (response.status.isSuccess()) {
|
||||||
|
ctx.log.debug("action=send_message status=finished result=success chat_id={}", chatId)
|
||||||
ctx.self ! SendMessageSuccess
|
ctx.self ! SendMessageSuccess
|
||||||
} else {
|
} else {
|
||||||
|
ctx.log.error("action=send_message status=finished result=failure chat_id={} http_code={}", chatId, response.status.value)
|
||||||
ctx.self ! SendMessageFailure(new RuntimeException(s"Error while sending message. HTTP status: ${response.status}."))
|
ctx.self ! SendMessageFailure(new RuntimeException(s"Error while sending message. HTTP status: ${response.status}."))
|
||||||
}
|
}
|
||||||
case Failure(exception) => ctx.self ! SendMessageFailure(exception)
|
case Failure(exception) =>
|
||||||
|
ctx.log.error(exception, "action=send_message status=finished result=failure chat_id={}", chatId)
|
||||||
|
ctx.self ! SendMessageFailure(exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
Behaviors.receiveMessage {
|
Behaviors.receiveMessage {
|
||||||
case SendMessageSuccess =>
|
case SendMessageSuccess =>
|
||||||
stashBuffer.unstashAll(ctx, onSuccess)
|
stashBuffer.unstashAll(ctx, onSuccess)
|
||||||
case SendMessageFailure(exception) =>
|
case SendMessageFailure(exception) =>
|
||||||
|
if (attempt >= 5) {
|
||||||
ctx.log.error(exception, "action=send_message result=failure")
|
ctx.log.error(exception, "action=send_message result=failure")
|
||||||
stashBuffer.unstashAll(ctx, onFailure)
|
stashBuffer.unstashAll(ctx, onFailure)
|
||||||
|
} else {
|
||||||
|
sendMessage(message, onSuccess, onFailure, attempt + 1)
|
||||||
|
}
|
||||||
case otherMsg =>
|
case otherMsg =>
|
||||||
stashBuffer.stash(otherMsg)
|
stashBuffer.stash(otherMsg)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package eu.xeppaka.bot
|
package eu.xeppaka.bot
|
||||||
|
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
@@ -12,8 +13,9 @@ import akka.http.scaladsl.model.headers.{Accept, `User-Agent`}
|
|||||||
import akka.http.scaladsl.settings.{ClientConnectionSettings, ConnectionPoolSettings}
|
import akka.http.scaladsl.settings.{ClientConnectionSettings, ConnectionPoolSettings}
|
||||||
import akka.http.scaladsl.unmarshalling.Unmarshal
|
import akka.http.scaladsl.unmarshalling.Unmarshal
|
||||||
import akka.http.scaladsl.{Http, HttpsConnectionContext}
|
import akka.http.scaladsl.{Http, HttpsConnectionContext}
|
||||||
import akka.persistence.typed.scaladsl.PersistentBehaviors.{CommandHandler, EventHandler}
|
import akka.persistence.typed.PersistenceId
|
||||||
import akka.persistence.typed.scaladsl.{Effect, PersistentBehaviors}
|
import akka.persistence.typed.scaladsl.EventSourcedBehavior.{CommandHandler, EventHandler}
|
||||||
|
import akka.persistence.typed.scaladsl.{Effect, EventSourcedBehavior}
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import com.typesafe.sslconfig.akka.AkkaSSLConfig
|
import com.typesafe.sslconfig.akka.AkkaSSLConfig
|
||||||
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
|
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
|
||||||
@@ -26,6 +28,7 @@ import scala.concurrent.duration._
|
|||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
object Entities {
|
object Entities {
|
||||||
|
|
||||||
case class Attributes(
|
case class Attributes(
|
||||||
parcelType: String,
|
parcelType: String,
|
||||||
weight: Double,
|
weight: Double,
|
||||||
@@ -43,10 +46,7 @@ object Entities {
|
|||||||
latitude: Option[Double],
|
latitude: Option[Double],
|
||||||
longitude: Option[Double],
|
longitude: Option[Double],
|
||||||
timeDeliveryAttempt: Option[String]
|
timeDeliveryAttempt: Option[String]
|
||||||
) {
|
)
|
||||||
def prettyPrint: String =
|
|
||||||
s"$date\n$text"
|
|
||||||
}
|
|
||||||
|
|
||||||
case class States(state: Seq[State])
|
case class States(state: Seq[State])
|
||||||
|
|
||||||
@@ -54,13 +54,18 @@ object Entities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object CzechPostDeliveryCheck {
|
object CzechPostDeliveryCheck {
|
||||||
|
private val czechPostDateFormat = new SimpleDateFormat("yyyy-MM-dd")
|
||||||
|
private val printDateFormat = new SimpleDateFormat("dd-MM-yyyy")
|
||||||
|
|
||||||
sealed trait Command
|
sealed trait Command
|
||||||
sealed trait CommandResult
|
sealed trait CommandResult
|
||||||
sealed trait Event
|
sealed trait Event
|
||||||
case class ParcelState(attributes: Option[Entities.Attributes] = None, states: Set[Entities.State] = Set.empty) {
|
case class ParcelState(attributes: Option[Entities.Attributes] = None, states: Set[Entities.State] = Set.empty) {
|
||||||
def prettyPrint(parcelId: String): String = {
|
def prettyPrint(parcelId: String): String = {
|
||||||
val statesString = states
|
val statesString = states
|
||||||
.map(state => s"${state.prettyPrint}\n===========================\n")
|
.toSeq
|
||||||
|
.sortBy(state => czechPostDateFormat.parse(state.date))
|
||||||
|
.map(state => s"${printDateFormat.format(czechPostDateFormat.parse(state.date))} - ${state.text}\n===========================\n")
|
||||||
.mkString
|
.mkString
|
||||||
|
|
||||||
s"""|*New state(s) of the parcel $parcelId:*
|
s"""|*New state(s) of the parcel $parcelId:*
|
||||||
@@ -73,7 +78,7 @@ object CzechPostDeliveryCheck {
|
|||||||
case class AddParcel(parcelId: String, replyTo: ActorRef[CommandResult]) extends Command
|
case class AddParcel(parcelId: String, replyTo: ActorRef[CommandResult]) extends Command
|
||||||
case class RemoveParcel(parcelId: String, replyTo: ActorRef[CommandResult]) extends Command
|
case class RemoveParcel(parcelId: String, replyTo: ActorRef[CommandResult]) extends Command
|
||||||
case class ListParcels(replyTo: ActorRef[ListParcelsResult]) extends Command
|
case class ListParcels(replyTo: ActorRef[ListParcelsResult]) extends Command
|
||||||
case class ListParcelsResult(parcelsList: String)
|
case class ListParcelsResult(parcelsList: Set[String])
|
||||||
|
|
||||||
case object CommandResultSuccess extends CommandResult
|
case object CommandResultSuccess extends CommandResult
|
||||||
case class CommandResultFailure(exception: Throwable) extends CommandResult
|
case class CommandResultFailure(exception: Throwable) extends CommandResult
|
||||||
@@ -133,31 +138,33 @@ object CzechPostDeliveryCheck {
|
|||||||
val commandHandler: CommandHandler[Command, Event, State] = (state, cmd) => {
|
val commandHandler: CommandHandler[Command, Event, State] = (state, cmd) => {
|
||||||
cmd match {
|
cmd match {
|
||||||
case AddParcel(parcelId, replyTo) =>
|
case AddParcel(parcelId, replyTo) =>
|
||||||
if (state.parcelStates.keySet.contains(parcelId)) {
|
val parcelIdUpper = parcelId.toUpperCase
|
||||||
|
if (state.parcelStates.keySet.contains(parcelIdUpper)) {
|
||||||
Effect
|
Effect
|
||||||
.none
|
.none
|
||||||
.thenRun(_ => replyTo ! CommandResultFailure(DuplicateParcelId(parcelId)))
|
.thenRun(_ => replyTo ! CommandResultFailure(DuplicateParcelId(parcelIdUpper)))
|
||||||
} else {
|
} else {
|
||||||
Effect
|
Effect
|
||||||
.persist(ParcelAdded(parcelId))
|
.persist(ParcelAdded(parcelIdUpper))
|
||||||
.thenRun(_ => {
|
.thenRun(_ => {
|
||||||
replyTo ! CommandResultSuccess
|
replyTo ! CommandResultSuccess
|
||||||
ctx.self ! CheckParcels
|
ctx.self ! CheckParcels
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case RemoveParcel(parcelId, replyTo) =>
|
case RemoveParcel(parcelId, replyTo) =>
|
||||||
if (state.parcelStates.keySet.contains(parcelId)) {
|
val parcelIdUpper = parcelId.toUpperCase
|
||||||
|
if (state.parcelStates.keySet.contains(parcelIdUpper)) {
|
||||||
Effect
|
Effect
|
||||||
.persist(ParcelRemoved(parcelId))
|
.persist(ParcelRemoved(parcelIdUpper))
|
||||||
.thenRun(_ => replyTo ! CommandResultSuccess)
|
.thenRun(_ => replyTo ! CommandResultSuccess)
|
||||||
} else {
|
} else {
|
||||||
Effect
|
Effect
|
||||||
.none
|
.none
|
||||||
.thenRun(_ => replyTo ! CommandResultFailure(ParcelIdNotFound(parcelId)))
|
.thenRun(_ => replyTo ! CommandResultFailure(ParcelIdNotFound(parcelIdUpper)))
|
||||||
}
|
}
|
||||||
|
|
||||||
case ListParcels(replyTo) =>
|
case ListParcels(replyTo) =>
|
||||||
val parcelsList = "*List of your watched parcels:*\n" + (if (state.parcelStates.keys.nonEmpty) state.parcelStates.keys.toSeq.sorted.map(id => id + "\n").mkString else "(empty)")
|
val parcelsList = state.parcelStates.keySet
|
||||||
Effect.none
|
Effect.none
|
||||||
.thenRun(_ => replyTo ! ListParcelsResult(parcelsList))
|
.thenRun(_ => replyTo ! ListParcelsResult(parcelsList))
|
||||||
|
|
||||||
@@ -229,8 +236,8 @@ object CzechPostDeliveryCheck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentBehaviors.receive[Command, Event, State](
|
EventSourcedBehavior[Command, Event, State](
|
||||||
persistenceId = s"$chatId-czechpost",
|
persistenceId = PersistenceId(s"$chatId-czechpost"),
|
||||||
emptyState = State(),
|
emptyState = State(),
|
||||||
commandHandler = commandHandler,
|
commandHandler = commandHandler,
|
||||||
eventHandler = eventHandler
|
eventHandler = eventHandler
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import scala.io.StdIn
|
|||||||
|
|
||||||
object Main {
|
object Main {
|
||||||
def main(args: Array[String]): Unit = {
|
def main(args: Array[String]): Unit = {
|
||||||
//val botId = "570855144:AAEv7b817cuq2JJI9f2kG5B9G3zW1x-btz4" // useless bot
|
val botId = "570855144:AAEv7b817cuq2JJI9f2kG5B9G3zW1x-btz4" // useless bot
|
||||||
val botId = "693134480:AAE8JRXA6j1mkOKTaxapP6A-E4LPHRuiIf8" // delivery bot
|
//val botId = "693134480:AAE8JRXA6j1mkOKTaxapP6A-E4LPHRuiIf8" // delivery bot
|
||||||
val telegramBot = ActorSystem(TelegramBot.behavior(botId, "0.0.0.0", 8443), "telegram-bot")
|
val telegramBot = ActorSystem(TelegramBot.behavior(botId, "0.0.0.0", 8443), "telegram-bot")
|
||||||
implicit val actorSystem: actor.ActorSystem = telegramBot.toUntyped
|
implicit val actorSystem: actor.ActorSystem = telegramBot.toUntyped
|
||||||
implicit val executionContext: ExecutionContextExecutor = telegramBot.dispatchers.lookup(DispatcherSelector.default())
|
implicit val executionContext: ExecutionContextExecutor = telegramBot.dispatchers.lookup(DispatcherSelector.default())
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package eu.xeppaka.bot
|
||||||
|
|
||||||
|
sealed trait PostType
|
||||||
|
|
||||||
|
object PostTypes {
|
||||||
|
case object CzechPost extends PostType
|
||||||
|
case object PplPost extends PostType
|
||||||
|
}
|
||||||
@@ -10,6 +10,35 @@ object TelegramEntities {
|
|||||||
|
|
||||||
case class GetMe(id: Int, is_bot: Boolean, first_name: String, username: String)
|
case class GetMe(id: Int, is_bot: Boolean, first_name: String, username: String)
|
||||||
|
|
||||||
|
case class KeyboardButton(text: String,
|
||||||
|
request_contact: Option[Boolean] = None,
|
||||||
|
request_location: Option[Boolean] = None
|
||||||
|
)
|
||||||
|
|
||||||
|
case class InlineKeyboardButton(text: String,
|
||||||
|
url: Option[String] = None,
|
||||||
|
callback_data: Option[String] = None,
|
||||||
|
switch_inline_query: Option[String] = None,
|
||||||
|
switch_inline_query_current_chat: Option[String] = None,
|
||||||
|
callback_game: Option[String] = None,
|
||||||
|
pay: Option[Boolean] = None
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed trait ReplyMarkup
|
||||||
|
|
||||||
|
case class ReplyKeyboardRemove(remove_keyboard: Boolean = true, selective: Option[Boolean] = None) extends ReplyMarkup
|
||||||
|
|
||||||
|
case class ReplyKeyboardMarkup(keyboard: Seq[Seq[KeyboardButton]],
|
||||||
|
resize_keyboard: Option[Boolean] = None,
|
||||||
|
one_time_keyboard: Option[Boolean] = None,
|
||||||
|
selective: Option[Boolean] = None
|
||||||
|
) extends ReplyMarkup
|
||||||
|
|
||||||
|
case class InlineKeyboardMarkup(inline_keyboard: Seq[Seq[InlineKeyboardButton]])
|
||||||
|
extends ReplyMarkup
|
||||||
|
|
||||||
|
case class ForceReply(force_reply: Boolean = true, selective: Option[Boolean] = None) extends ReplyMarkup
|
||||||
|
|
||||||
case class InlineQuery(id: String,
|
case class InlineQuery(id: String,
|
||||||
from: User,
|
from: User,
|
||||||
location: Location,
|
location: Location,
|
||||||
@@ -92,7 +121,7 @@ object TelegramEntities {
|
|||||||
disable_web_page_preview: Option[Boolean] = None,
|
disable_web_page_preview: Option[Boolean] = None,
|
||||||
disable_notification: Option[Boolean] = None,
|
disable_notification: Option[Boolean] = None,
|
||||||
reply_to_message_id: Option[Int] = None,
|
reply_to_message_id: Option[Int] = None,
|
||||||
reply_markup: Option[String] = None
|
reply_markup: Option[ReplyMarkup] = None
|
||||||
)
|
)
|
||||||
|
|
||||||
case class Message(message_id: Int,
|
case class Message(message_id: Int,
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package eu.xeppaka.bot
|
||||||
|
|
||||||
|
import cats.syntax.functor._
|
||||||
|
import eu.xeppaka.bot.TelegramEntities._
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
import io.circe.generic.auto._
|
||||||
|
import io.circe.syntax._
|
||||||
|
|
||||||
|
object TelegramEntitiesDerivations {
|
||||||
|
implicit val encodeReplyMarkup: Encoder[ReplyMarkup] = Encoder.instance {
|
||||||
|
case replyKeyboardMarkup: ReplyKeyboardMarkup => replyKeyboardMarkup.asJson
|
||||||
|
case replyKeyboardRemove: ReplyKeyboardRemove => replyKeyboardRemove.asJson
|
||||||
|
case inlineKeyboardMarkup: InlineKeyboardMarkup => inlineKeyboardMarkup.asJson
|
||||||
|
case forceReply: ForceReply => forceReply.asJson
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val decodeReplyMarkup: Decoder[ReplyMarkup] =
|
||||||
|
List[Decoder[ReplyMarkup]](
|
||||||
|
Decoder[ReplyKeyboardMarkup].widen,
|
||||||
|
Decoder[ReplyKeyboardRemove].widen,
|
||||||
|
Decoder[InlineKeyboardMarkup].widen,
|
||||||
|
Decoder[ForceReply].widen
|
||||||
|
).reduceLeft(_ or _)
|
||||||
|
}
|
||||||
16
telegram-bot/src/test/scala/eu/xeppaka/bot/JsonSpec.scala
Normal file
16
telegram-bot/src/test/scala/eu/xeppaka/bot/JsonSpec.scala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package eu.xeppaka.bot
|
||||||
|
|
||||||
|
import eu.xeppaka.bot.TelegramEntities._
|
||||||
|
import io.circe.Printer
|
||||||
|
import io.circe.generic.auto._
|
||||||
|
import io.circe.syntax._
|
||||||
|
import org.scalatest.FlatSpec
|
||||||
|
import TelegramEntitiesDerivations._
|
||||||
|
|
||||||
|
class JsonSpec extends FlatSpec {
|
||||||
|
"blah" should "blah" in {
|
||||||
|
val keyboard = ReplyKeyboardRemove()
|
||||||
|
val message = SendMessage(100000, "Please enter command.", reply_markup = Some(keyboard))
|
||||||
|
println(message.asJson.pretty(Printer.spaces2.copy(dropNullValues = true)))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user