Compare commits

..

2 Commits

Author SHA1 Message Date
Pavel Kachalouski
f8f1752d7c Cassandra database used
Some checks failed
continuous-integration/drone/push Build was killed
2019-11-03 20:28:02 +01:00
Pavel Kachalouski
b5917d92af Added comment to the parcel 2019-10-27 20:39:50 +01:00
11 changed files with 102 additions and 412 deletions

View File

@@ -2,8 +2,7 @@ import Dependencies._
lazy val commonSettings = Seq(
organization := "com.example",
scalaVersion := "2.12.8",
version := "1.0.0",
scalaVersion := "2.13.1",
mainClass := Some("eu.xeppaka.bot.Main")
)
@@ -19,11 +18,13 @@ lazy val `telegram-bot-delivery` = (project in file("."))
akkaHttp,
akkaStream,
akkaPersistence,
akkaPersistenceCassandra,
levelDbJni,
circleCore,
circleGeneric,
circleParser,
circeAkkaHttp
circeAkkaHttp,
slibTelegram
),
dockerBaseImage := "openjdk:13-jdk-oracle",
dockerExposedPorts := Seq(8443),
@@ -31,6 +32,7 @@ lazy val `telegram-bot-delivery` = (project in file("."))
Docker / daemonUserUid := Some("1001"),
Docker / daemonUser := "telegram-bot",
Docker / defaultLinuxInstallLocation := "/opt/telegram-bot-delivery",
version := "1.0.1"
)
.enablePlugins(JavaServerAppPackaging)
.enablePlugins(DockerPlugin)

View File

@@ -4,23 +4,27 @@ import Dependencies.Versions._
object Dependencies {
object Versions {
val akkaVersion = "2.5.22"
val akkaHttpVersion = "10.1.8"
val akkaVersion = "2.5.26"
val akkaHttpVersion = "10.1.10"
val akkaPersistenceCassandraVersion = "0.100"
val levelDbJniVersion = "1.8"
val circeVersion = "0.11.1"
val akkaHttpCirceVersion = "1.23.0"
val scalaTestVersion = "3.0.5"
val circeVersion = "0.12.3"
val akkaHttpCirceVersion = "1.29.1"
val scalaTestVersion = "3.2.0-M1"
val slibTelegramVersion = "0.1.0"
}
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
val akka = "com.typesafe.akka" %% "akka-actor" % akkaVersion
val akkaTyped = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion
val akkaStream = "com.typesafe.akka" %% "akka-stream" % akkaVersion
val akkaHttp = "com.typesafe.akka" %% "akka-http" % akkaHttpVersion
val akkaPersistence = "com.typesafe.akka" %% "akka-persistence-typed" % akkaVersion
val akkaPersistenceCassandra = "com.typesafe.akka" %% "akka-persistence-cassandra" % "0.100"
val levelDbJni = "org.fusesource.leveldbjni" % "leveldbjni-all" % levelDbJniVersion
val circleCore = "io.circe" %% "circe-core" % circeVersion
val circleGeneric = "io.circe" %% "circe-generic" % circeVersion
val circleParser = "io.circe" %% "circe-parser" % circeVersion
val circeAkkaHttp = "de.heikoseeberger" %% "akka-http-circe" % akkaHttpCirceVersion
val slibTelegram = "eu.xeppaka" %% "slib-telegram" % slibTelegramVersion
val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion
}

View File

@@ -1 +1 @@
sbt.version=1.2.8
sbt.version=1.3.3

View File

@@ -1,2 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.21")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.4.1")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.12")

View File

@@ -5,14 +5,11 @@ akka {
persistence {
journal {
plugin = "akka.persistence.journal.leveldb"
auto-start-journals = ["akka.persistence.journal.leveldb"]
leveldb.dir = "journal-check-delivery"
}
snapshot-store {
plugin = "akka.persistence.snapshot-store.local"
auto-start-snapshot-stores = ["akka.persistence.snapshot-store.local"]
plugin = "cassandra-journal"
}
}
}
cassandra-journal {
contact-points = ["cassandra"]
}

View File

@@ -8,8 +8,7 @@ import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import akka.util.{ByteString, Timeout}
import eu.xeppaka.bot.TelegramEntities._
import eu.xeppaka.bot.TelegramEntitiesDerivations._
import eu.xeppaka.telegram.bot.TelegramEntities._
import io.circe.Printer
import scala.concurrent.ExecutionContext
@@ -60,9 +59,9 @@ object CheckDeliveryDialog {
private val removeKeyboard = Some(ReplyKeyboardRemove())
def behavior(chatId: Long, botUri: BotUri): Behavior[Command] = Behaviors.setup[Command] { ctx =>
implicit val materializer: ActorMaterializer = ActorMaterializer()(ctx.system.toUntyped)
implicit val materializer: ActorMaterializer = ActorMaterializer()(ctx.system.toClassic)
implicit val executionContext: ExecutionContext = ctx.system.dispatchers.lookup(DispatcherSelector.default())
val http = Http()(ctx.system.toUntyped)
val http = Http()(ctx.system.toClassic)
val stashBuffer = StashBuffer[Command](100)
val deliveryStateAdapter: ActorRef[CzechPostDeliveryCheck.DeliveryStateChanged] = ctx.messageAdapter(stateChanged => DeliveryStateChanged(stateChanged.state))
val czechPostDeliveryCheck = ctx.spawnAnonymous(Behaviors.supervise(CzechPostDeliveryCheck.behavior(chatId.toString, deliveryStateAdapter)).onFailure(SupervisorStrategy.restart))
@@ -80,8 +79,9 @@ object CheckDeliveryDialog {
sendMessage(message, waitCommand, waitCommand)
}
case AddParcel =>
val message = SendMessage(chatId, "Please enter a parcel ID.", reply_markup = removeKeyboard)
sendMessage(message, waitParcelId(parcelId => addParcel(parcelId)), waitCommand)
val parcelIdMessage = SendMessage(chatId, "Please enter a parcel ID.", reply_markup = removeKeyboard)
val commentMessage = SendMessage(chatId, "Please enter a comment.", reply_markup = removeKeyboard)
sendMessage(parcelIdMessage, waitTextMessage(parcelId => sendMessage(commentMessage, waitTextMessage(comment => addParcel(parcelId, comment)), waitCommand)), waitCommand)
case RemoveParcel =>
removeParcel(waitCommand, waitCommand)
case ListParcels =>
@@ -96,12 +96,12 @@ object CheckDeliveryDialog {
Behaviors.unhandled
}
def addParcel(parcelId: String): Behavior[Command] = Behaviors.setup { ctx =>
def addParcel(parcelId: String, comment: String): Behavior[Command] = Behaviors.setup { ctx =>
case object AddParcelSuccess extends Command
case class AddParcelFailure(exception: Throwable) extends Command
implicit val timeout: Timeout = 5.seconds
ctx.ask[CzechPostDeliveryCheck.Command, CzechPostDeliveryCheck.CommandResult](czechPostDeliveryCheck)(ref => CzechPostDeliveryCheck.AddParcel(parcelId, ref)) {
ctx.ask[CzechPostDeliveryCheck.Command, CzechPostDeliveryCheck.CommandResult](czechPostDeliveryCheck)(ref => CzechPostDeliveryCheck.AddParcel(parcelId, comment, ref)) {
case Success(CzechPostDeliveryCheck.CommandResultSuccess) => AddParcelSuccess
case Success(CzechPostDeliveryCheck.CommandResultFailure(exception)) => AddParcelFailure(exception)
case Failure(exception) => AddParcelFailure(exception)
@@ -128,7 +128,7 @@ object CheckDeliveryDialog {
}
def listParcels: Behavior[Command] = Behaviors.setup { ctx =>
case class ListParcelsSuccess(parcelsList: Set[String]) extends Command
case class ListParcelsSuccess(parcelsList: Seq[String]) extends Command
case class ListParcelsFailure(exception: Throwable) extends Command
implicit val timeout: Timeout = 5.seconds
@@ -139,7 +139,7 @@ object CheckDeliveryDialog {
Behaviors.receiveMessage {
case ListParcelsSuccess(parcelsList) =>
val messageText = "*List of your watched parcels:*\n" + (if (parcelsList.nonEmpty) parcelsList.toSeq.sorted.mkString("\n") else "(empty)")
val messageText = "*List of your watched parcels:*\n" + (if (parcelsList.nonEmpty) parcelsList.sorted.mkString("\n") else "(empty)")
val message = SendMessage(chatId, messageText, Some("Markdown"), reply_markup = commandsKeyboard)
sendMessage(message, waitCommand, waitCommand)
case ListParcelsFailure(exception) =>
@@ -154,27 +154,27 @@ object CheckDeliveryDialog {
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
case class ListParcelIdsSuccess(parcelsList: Seq[String]) extends Command
case class ListParcelIdsFailure(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)
ctx.ask[CzechPostDeliveryCheck.Command, CzechPostDeliveryCheck.ListParcelIdsResult](czechPostDeliveryCheck)(ref => CzechPostDeliveryCheck.ListParcelIds(ref)) {
case Success(CzechPostDeliveryCheck.ListParcelIdsResult(parcelsList)) => ListParcelIdsSuccess(parcelsList)
case Failure(exception) => ListParcelIdsFailure(exception)
}
Behaviors.receiveMessage {
case ListParcelsSuccess(parcelsList) =>
case ListParcelIdsSuccess(parcelsList) =>
if (parcelsList.nonEmpty) {
val keyboardButtons = parcelsList.toSeq.sorted.grouped(3).map(_.map(id => KeyboardButton(id))).toSeq
val keyboardButtons = parcelsList.sorted.grouped(3).map(_.map(id => KeyboardButton(id))).toSeq
val markup = ReplyKeyboardMarkup(keyboard = keyboardButtons, resize_keyboard = Some(true), one_time_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)
sendMessage(message, waitTextMessage(parcelId => removeParcelId(parcelId)), onFailure)
} else {
val message = SendMessage(chatId, "You don't have watched parcels. There is nothing to remove.", reply_markup = commandsKeyboard)
sendMessage(message, onSuccess, onFailure)
}
case ListParcelsFailure(exception) =>
case ListParcelIdsFailure(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.", reply_markup = commandsKeyboard)
sendMessage(message, waitCommand, waitCommand)
@@ -225,7 +225,7 @@ object CheckDeliveryDialog {
// sendMessage(message, waitParcelId(parcelId => addParcel(parcelId)), waitCommand)
// }
def waitParcelId(onFinish: String => Behavior[Command]): Behavior[Command] = Behaviors.receiveMessage {
def waitTextMessage(onFinish: String => Behavior[Command]): Behavior[Command] = Behaviors.receiveMessage {
case ProcessMessage(msg, replyTo) =>
if (msg.text.isDefined) {
val parcelId = msg.text.get
@@ -233,7 +233,7 @@ object CheckDeliveryDialog {
onFinish(parcelId)
} else {
replyTo ! ProcessMessageSuccess
waitParcelId(onFinish)
waitTextMessage(onFinish)
}
case otherMsg =>
stashBuffer.stash(otherMsg)
@@ -247,7 +247,7 @@ object CheckDeliveryDialog {
case object SendMessageSuccess extends Command
case class SendMessageFailure(exception: Throwable) extends Command
val json = printer.pretty(message.asJson)
val json = printer.print(message.asJson)
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)

View File

@@ -60,25 +60,39 @@ object CzechPostDeliveryCheck {
sealed trait Command
sealed trait CommandResult
sealed trait Event
case class ParcelState(attributes: Option[Entities.Attributes] = None, states: Set[Entities.State] = Set.empty) {
def prettyPrint(parcelId: String): String = {
case class Parcel(comment: String, attributes: Option[Entities.Attributes] = None, states: Set[Entities.State] = Set.empty) {
def fullStatePrint(parcelId: String): String = {
val statesString = states
.toSeq
.sortBy(state => czechPostDateFormat.parse(state.date))
.map(state => s"${printDateFormat.format(czechPostDateFormat.parse(state.date))} - ${state.text}\n===========================\n")
.mkString
s"""|*New state(s) of the parcel $parcelId:*
s"""|*New state(s) of the parcel $parcelId ($comment):*
|===========================
|$statesString""".stripMargin
}
}
case class State(parcelStates: Map[String, ParcelState] = Map.empty)
case class AddParcel(parcelId: String, replyTo: ActorRef[CommandResult]) extends Command
def latestStatePrint(parcelId: String): String = {
val state = latestState
s"$parcelId ($comment) - ${printDateFormat.format(czechPostDateFormat.parse(state.date))} - ${state.text}"
}
private def latestState: Entities.State = states.toSeq.maxBy(state => czechPostDateFormat.parse(state.date))
}
case class State(parcelStates: Map[String, Parcel] = Map.empty) {
def latestStatesPrint: Seq[String] = parcelStates
.map { case (id, parcel) => parcel.latestStatePrint(id) }
.to(Seq)
}
case class AddParcel(parcelId: String, comment: 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: Set[String])
case class ListParcelsResult(parcelsList: Seq[String])
case class ListParcelIds(replyTo: ActorRef[ListParcelIdsResult]) extends Command
case class ListParcelIdsResult(parcelIds: Seq[String])
case object CommandResultSuccess extends CommandResult
case class CommandResultFailure(exception: Throwable) extends CommandResult
@@ -91,7 +105,7 @@ object CzechPostDeliveryCheck {
private case class ParcelHistoryRetrieved(parcelHistory: Entities.ParcelHistory) extends Command
case class DeliveryStateChanged(state: String)
case class ParcelAdded(parcelId: String) extends Event
case class ParcelAdded(parcelId: String, comment: String) extends Event
case class ParcelRemoved(parcelId: String) extends Event
case class ParcelHistoryStateAdded(parcelId: String, state: Entities.State) extends Event
case class ParcelAttributesChanged(parcelId: String, attributes: Entities.Attributes) extends Event
@@ -137,7 +151,7 @@ object CzechPostDeliveryCheck {
val commandHandler: CommandHandler[Command, Event, State] = (state, cmd) => {
cmd match {
case AddParcel(parcelId, replyTo) =>
case AddParcel(parcelId, comment, replyTo) =>
val parcelIdUpper = parcelId.toUpperCase
if (state.parcelStates.keySet.contains(parcelIdUpper)) {
Effect
@@ -145,7 +159,7 @@ object CzechPostDeliveryCheck {
.thenRun(_ => replyTo ! CommandResultFailure(DuplicateParcelId(parcelIdUpper)))
} else {
Effect
.persist(ParcelAdded(parcelIdUpper))
.persist(ParcelAdded(parcelIdUpper, comment))
.thenRun(_ => {
replyTo ! CommandResultSuccess
ctx.self ! CheckParcels
@@ -166,10 +180,16 @@ object CzechPostDeliveryCheck {
case ListParcels(replyTo) =>
Effect.none
.thenRun { state =>
val parcelsList = state.parcelStates.keySet
val parcelsList = state.latestStatesPrint
replyTo ! ListParcelsResult(parcelsList)
}
case ListParcelIds(replyTo) =>
Effect.none
.thenRun { state =>
replyTo ! ListParcelIdsResult(state.parcelStates.keys.toSeq)
}
case CheckParcels =>
Effect
.none
@@ -205,21 +225,25 @@ object CzechPostDeliveryCheck {
case ParcelHistoryRetrieved(parcelHistory) =>
val parcelId = parcelHistory.id
val parcelState = state.parcelStates(parcelId)
val attributesChangedEvent = (if (parcelState.attributes.isEmpty)
val attributesChangedEvents: Seq[Event] = (if (parcelState.attributes.isEmpty)
Some(parcelHistory.attributes)
else
parcelState.attributes
.flatMap(oldAttributes => if (oldAttributes != parcelHistory.attributes) Some(parcelHistory.attributes) else None))
.map(attributes => ParcelAttributesChanged(parcelId, attributes)).to[collection.immutable.Seq]
.map(attributes => ParcelAttributesChanged(parcelId, attributes))
.toSeq
val newStates = parcelHistory.states.state.toSet -- parcelState.states
val stateEvents: Seq[Event] = newStates.map(state => ParcelHistoryStateAdded(parcelId, state)).to[collection.immutable.Seq]
val stateEvents: Seq[Event] = newStates
.map(state => ParcelHistoryStateAdded(parcelId, state))
.toSeq
val comment = state.parcelStates(parcelId).comment
Effect
.persist(attributesChangedEvent ++ stateEvents)
.persist(attributesChangedEvents ++ stateEvents)
.thenRun(_ => {
if (newStates.nonEmpty) {
stateReporter ! DeliveryStateChanged(ParcelState(None, newStates).prettyPrint(parcelId))
stateReporter ! DeliveryStateChanged(Parcel(comment, None, newStates).fullStatePrint(parcelId))
}
})
}
@@ -227,7 +251,8 @@ object CzechPostDeliveryCheck {
val eventHandler: EventHandler[State, Event] = (state, evt) => {
evt match {
case ParcelAdded(parcelId) => state.copy(parcelStates = state.parcelStates + (parcelId -> ParcelState()))
case ParcelAdded(parcelId, comment) =>
state.copy(parcelStates = state.parcelStates + (parcelId -> Parcel(comment)))
case ParcelRemoved(parcelId) => state.copy(parcelStates = state.parcelStates - parcelId)
case ParcelHistoryStateAdded(parcelId, newState) =>
val parcelState = state.parcelStates(parcelId)

View File

@@ -7,7 +7,7 @@ import akka.persistence.typed.scaladsl.EventSourcedBehavior.{CommandHandler, Eve
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
import eu.xeppaka.telegram.bot.TelegramEntities._
import scala.concurrent.duration._
import scala.util.{Failure, Success}

View File

@@ -1,15 +1,14 @@
package eu.xeppaka.bot
import java.io.InputStream
import java.nio.file.Path
import java.security.{KeyStore, SecureRandom}
import java.util.UUID
import akka.Done
import akka.actor.{ActorSystem, Scheduler}
import akka.actor.typed.scaladsl.adapter._
import akka.actor.typed.scaladsl.{Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, DispatcherSelector, SupervisorStrategy}
import akka.actor.{ActorSystem, Scheduler}
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives.{as, complete, entity, extractLog, onComplete, path, post}
@@ -17,11 +16,11 @@ import akka.http.scaladsl.server.Route
import akka.http.scaladsl.{ConnectionContext, Http, HttpExt, HttpsConnectionContext}
import akka.stream.ActorMaterializer
import akka.util.{ByteString, Timeout}
import eu.xeppaka.bot.TelegramEntities._
import eu.xeppaka.telegram.bot.TelegramEntities._
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
import scala.collection.immutable
import scala.concurrent.{ExecutionContextExecutor, Future}
import scala.concurrent.ExecutionContextExecutor
import scala.concurrent.duration._
import scala.io.Source
import scala.util.{Failure, Success}
@@ -38,7 +37,7 @@ object TelegramBot {
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
implicit val untypedSystem: ActorSystem = ctx.system.toClassic
implicit val actorMaterializer: ActorMaterializer = ActorMaterializer()
implicit val executionContextExecutor: ExecutionContextExecutor = ctx.system.dispatchers.lookup(DispatcherSelector.default())

View File

@@ -1,313 +0,0 @@
package eu.xeppaka.bot
object TelegramEntities {
case class Response[T](ok: Boolean,
description: Option[String] = None,
error_code: Option[Int] = None,
result: T
)
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,
from: User,
location: Location,
query: String,
offset: String
)
case class Location(longitude: Float,
latitude: Float
)
case class Update(update_id: Int,
message: Option[Message] = None,
edited_message: Option[Message] = None,
channel_post: Option[Message] = None,
edited_channel_post: Option[Message] = None,
inline_query: Option[InlineQuery] = None,
chosen_inline_result: Option[ChosenInlineResult] = None,
callback_query: Option[CallbackQuery] = None,
shipping_query: Option[ShippingQuery] = None,
pre_checkout_query: Option[PreCheckoutQuery] = None
)
case class ChosenInlineResult(result_id: String,
from: User,
location: Option[Location] = None,
inline_message_id: Option[String] = None,
query: String
)
case class CallbackQuery(id: String,
from: User,
message: Option[Message] = None,
inline_message_id: Option[String] = None,
chat_instance: String,
data: Option[String] = None,
game_short_name: Option[String] = None
)
case class ShippingQuery(id: String,
from: User,
invoice_payload: String,
shipping_address: ShippingAddress
)
case class ShippingAddress(country_code: String,
state: String,
city: String,
street_line1: String,
street_line2: String,
post_code: String
)
case class PreCheckoutQuery(id: String,
from: User,
currency: String,
total_amount: Int,
invoice_payload: String,
shipping_option_id: Option[String] = None,
order_info: Option[OrderInfo] = None
)
case class OrderInfo(name: Option[String] = None,
phone_number: Option[String] = None,
email: Option[String] = None,
shipping_address: Option[ShippingAddress] = None
)
case class User(id: Int,
is_bot: Boolean,
first_name: String,
last_name: Option[String] = None,
username: Option[String] = None,
language_code: Option[String] = None
)
case class EditMessageReplyMarkup(chat_id: Option[Long],
message_id: Option[Int],
inline_message_id: Option[String],
reply_markup: Option[InlineKeyboardMarkup]
)
case class SendMessage(chat_id: Long,
text: String,
parse_mode: Option[String] = None,
disable_web_page_preview: Option[Boolean] = None,
disable_notification: Option[Boolean] = None,
reply_to_message_id: Option[Int] = None,
reply_markup: Option[ReplyMarkup] = None
)
case class Message(message_id: Int,
from: Option[User] = None,
date: Int,
chat: Chat,
forward_from: Option[User] = None,
forward_from_chat: Option[User] = None,
forward_from_message_id: Option[Int] = None,
forward_signature: Option[String] = None,
forward_date: Option[Int] = None,
reply_to_message: Option[Message] = None,
edit_date: Option[Int] = None,
media_group_id: Option[String] = None,
author_signature: Option[String] = None,
text: Option[String] = None,
entities: Option[Seq[MessageEntity]] = None,
caption_entities: Option[Seq[MessageEntity]] = None,
audio: Option[Audio] = None,
document: Option[Document] = None,
game: Option[Game] = None,
photo: Option[Seq[PhotoSize]] = None,
sticker: Option[Sticker] = None,
video: Option[Video] = None,
voice: Option[Voice] = None,
video_note: Option[VideoNote] = None,
caption: Option[String] = None,
contact: Option[Contact] = None,
location: Option[Location] = None,
venue: Option[Venue] = None,
new_chat_members: Option[Seq[User]] = None,
left_chat_member: Option[Seq[User]] = None,
new_chat_title: Option[String] = None,
new_chat_photo: Option[Seq[PhotoSize]] = None,
delete_chat_photo: Option[Boolean] = None,
group_chat_created: Option[Boolean] = None,
supergroup_chat_created: Option[Boolean] = None,
channel_chat_created: Option[Boolean] = None,
migrate_to_chat_id: Option[Int] = None,
migrate_from_chat_id: Option[Int] = None,
pinned_message: Option[Message] = None,
invoice: Option[Invoice] = None,
successful_payment: Option[SuccessfulPayment] = None,
connected_website: Option[String] = None
)
case class MessageEntity(`type`: String,
offset: Int,
length: Int,
url: Option[String] = None,
user: Option[User] = None
)
case class Contact(phone_number: String,
first_name: String,
last_name: Option[String] = None,
user_id: Option[Int] = None
)
case class Sticker(file_id: String,
width: Int,
height: Int,
thumb: Option[PhotoSize] = None,
emoji: Option[String] = None,
set_name: Option[String] = None,
mask_position: Option[String] = None,
file_size: Option[Int] = None
)
case class Video(file_id: String,
width: Int,
height: Int,
duration: Int,
thumb: Option[PhotoSize] = None,
mime_type: Option[String] = None,
file_size: Option[Int] = None
)
case class Audio(file_id: String,
duration: Int,
performer: Option[String] = None,
title: Option[String] = None,
mime_type: Option[String] = None,
file_size: Option[Int] = None
)
case class Document(file_id: String,
thumb: Option[PhotoSize] = None,
file_name: Option[String] = None,
mime_type: Option[String] = None,
file_size: Option[Int] = None
)
case class PhotoSize(file_id: String,
width: Int,
height: Int,
file_size: Option[Int] = None
)
case class Voice(file_id: String,
duration: Int,
mime_type: Option[String] = None,
file_size: Option[Int] = None
)
case class VideoNote(file_id: String,
length: Int,
duration: Int,
thumb: Option[PhotoSize] = None,
file_size: Option[Int] = None
)
case class ChatPhoto(small_file_id: String, big_file_id: String)
case class Chat(id: Long,
`type`: String,
title: Option[String] = None,
username: Option[String] = None,
first_name: Option[String] = None,
last_name: Option[String] = None,
all_members_are_administrators: Option[Boolean] = None,
photo: Option[ChatPhoto] = None,
description: Option[String] = None,
invite_link: Option[String] = None,
pinned_message: Option[Message] = None,
sticker_set_name: Option[String] = None,
can_set_sticker_set: Option[Boolean] = None
)
case class Game(title: String,
description: String,
photo: Seq[PhotoSize],
text: Option[String] = None,
text_entities: Option[Seq[MessageEntity]] = None,
animation: Option[Animation] = None
)
case class Animation(file_id: String,
thumb: Option[PhotoSize] = None,
file_name: Option[String] = None,
mime_type: Option[String] = None,
file_size: Option[Int] = None
)
case class InputFile()
case class Venue(location: Location,
title: String,
address: String,
foursquare_id: Option[String] = None
)
case class Invoice(title: String,
description: String,
start_parameter: String,
currency: String,
total_amount: Int
)
case class SuccessfulPayment(currency: String,
total_amount: Int,
invoice_payload: String,
shipping_option_id: Option[String] = None,
order_info: Option[OrderInfo] = None,
telegram_payment_charge_id: String,
provider_payment_charge_id: String
)
case class Webhook(url: String,
certificate: Option[InputFile] = None,
max_connections: Option[Int] = None,
allowed_updates: Option[Seq[String]] = None
)
case class WebhookInfo(url: String,
has_custom_certificate: Boolean,
pending_update_count: Int,
last_error_date: Option[Int] = None,
last_error_message: Option[String] = None,
max_connections: Option[Int] = None,
allowed_updates: Option[Seq[String]] = None
)
}

View File

@@ -1,24 +0,0 @@
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 _)
}