diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala b/telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala index 85e6a09..11770ae 100644 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala +++ b/telegram-bot/src/main/scala/eu/xeppaka/bot/CheckDeliveryDialog.scala @@ -61,7 +61,8 @@ object CheckDeliveryDialog { sendMessage("Please enter parcel ID.", waitParcelId(parcelId => addParcel(parcelId)), initial) case RemoveParcel => sendMessage("Please enter parcel ID.", waitParcelId(parcelId => removeParcel(parcelId)), initial) - case ListParcels => sendMessage("This command is not supported yet.", initial, initial) + case ListParcels => + listParcels case Help => sendMessage("Supported commands: /add, /remove, /list, /help", initial, initial) case DeliveryStateChanged(state) => @@ -98,6 +99,28 @@ object CheckDeliveryDialog { } } + def listParcels: Behavior[Command] = Behaviors.setup { ctx => + case class ListParcelsSuccess(parcelsList: 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) => + sendMessage(parcelsList, initial, initial) + case ListParcelsFailure(exception) => + 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) + case otherMessage => + stashBuffer.stash(otherMessage) + Behaviors.same + } + } + def removeParcel(parcelId: String): Behavior[Command] = Behaviors.setup { ctx => case object RemoveParcelSuccess extends Command case class RemoveParcelFailure(exception: Throwable) extends Command diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala b/telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala index 318ae9e..b8e1af7 100644 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala +++ b/telegram-bot/src/main/scala/eu/xeppaka/bot/CzechPostDeliveryCheck.scala @@ -38,7 +38,7 @@ object Entities { text: String, postcode: Option[String], postoffice: Option[String], - idIcon: Option[String], + idIcon: Option[Int], publicAccess: Int, latitude: Option[Double], longitude: Option[Double], @@ -58,12 +58,12 @@ object CzechPostDeliveryCheck { sealed trait CommandResult sealed trait Event case class ParcelState(attributes: Option[Entities.Attributes] = None, states: Set[Entities.State] = Set.empty) { - def prettyPrint: String = { + def prettyPrint(parcelId: String): String = { val statesString = states .map(state => s"${state.prettyPrint}\n===========================\n") .mkString - s"""|*New state(s):* + s"""|*New state(s) of the parcel $parcelId:* |=========================== |$statesString""".stripMargin } @@ -72,6 +72,8 @@ object CzechPostDeliveryCheck { case class AddParcel(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 ListParcelsResult(parcelsList: String) case object CommandResultSuccess extends CommandResult case class CommandResultFailure(exception: Throwable) extends CommandResult @@ -154,13 +156,21 @@ object CzechPostDeliveryCheck { .thenRun(_ => replyTo ! CommandResultFailure(ParcelIdNotFound(parcelId))) } + 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)") + Effect.none + .thenRun(_ => replyTo ! ListParcelsResult(parcelsList)) + case CheckParcels => + ctx.log.info("action=check_parcel_state chat_id={}", chatId) val parcelIds = state.parcelStates.keys.grouped(10).map(ids => ids.foldLeft("")((acc, id) => if (acc.isEmpty) id else s"$acc;$id")) for (ids <- parcelIds) { val checkUri = Uri(s"https://b2c.cpost.cz/services/ParcelHistory/getDataAsJson?idParcel=$ids&language=en") val request = HttpRequest(uri = checkUri, headers = immutable.Seq(Accept(MediaTypes.`application/json`))) + ctx.log.info("action=check_parcel_state chat_id={} check_uri={}", chatId, checkUri) + http .singleRequest(request, connectionContext = sslContext, settings = connectionSettings) .transform { @@ -174,6 +184,10 @@ object CzechPostDeliveryCheck { case Failure(exception) => ctx.log.error(exception, "Error checking parcel history.") } + .andThen { + case Success(_) => ctx.log.info("action=check_parcel_state result=success chat_id={} check_uri={}", chatId, checkUri) + case Failure(exception) => ctx.log.error(exception, "action=check_parcel_state result=failure chat_id={} check_uri={}", chatId, checkUri) + } } Effect.none @@ -194,7 +208,7 @@ object CzechPostDeliveryCheck { .persist(attributesChangedEvent ++ stateEvents) .thenRun(_ => { if (newStates.nonEmpty) { - stateReporter ! DeliveryStateChanged(ParcelState(None, newStates).prettyPrint) + stateReporter ! DeliveryStateChanged(ParcelState(None, newStates).prettyPrint(parcelId)) } }) } diff --git a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala b/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala index ca50334..fc6e781 100644 --- a/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala +++ b/telegram-bot/src/main/scala/eu/xeppaka/bot/TelegramBot.scala @@ -11,7 +11,7 @@ import akka.actor.typed.scaladsl.{Behaviors, StashBuffer} import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector} import akka.http.scaladsl.marshalling.Marshal import akka.http.scaladsl.model._ -import akka.http.scaladsl.server.Directives.{as, entity, onComplete, path, post, complete} +import akka.http.scaladsl.server.Directives.{as, entity, onComplete, path, post, complete, extractLog} import akka.http.scaladsl.server.Route import akka.http.scaladsl.{ConnectionContext, Http, HttpExt, HttpsConnectionContext} import akka.stream.ActorMaterializer @@ -202,13 +202,19 @@ object TelegramBot { path(hookId) { post { - entity(as[Update]) { update => - onComplete(updatesProcessor.?[DialogManager.CommandResult](ref => DialogManager.ProcessUpdate(update, ref))) { - case Success(processResult) => processResult match { - case DialogManager.ProcessUpdateSuccess => complete(HttpResponse(status = StatusCodes.OK)) - case DialogManager.ProcessUpdateFailure(exception) => complete(HttpResponse(status = StatusCodes.InternalServerError)) + extractLog { log => + entity(as[Update]) { update => + onComplete(updatesProcessor.?[DialogManager.CommandResult](ref => DialogManager.ProcessUpdate(update, ref))) { + case Success(processResult) => processResult match { + case DialogManager.ProcessUpdateSuccess => complete(HttpResponse(status = StatusCodes.OK)) + case DialogManager.ProcessUpdateFailure(exception) => + log.error(exception, "action=process_update result=failure message={}", update) + complete(HttpResponse(status = StatusCodes.InternalServerError)) + } + case Failure(exception) => + log.error(exception, "action=process_update result=failure message={}", update) + complete(HttpResponse(status = StatusCodes.InternalServerError)) } - case Failure(exception) => complete(HttpResponse(status = StatusCodes.InternalServerError)) } } }