New JSON parsing library - circle. Integrated with akka-http. Test sending message by bot.
This commit is contained in:
22
.idea/modules/root-build.iml
generated
22
.idea/modules/root-build.iml
generated
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module external.linked.project.id="root-build" external.linked.project.path="$MODULE_DIR$/../../project" external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" sbt.imports="SUB:DOLLAR484980a9ef0855c97e1c.`root`, _root_.sbt.Keys._, _root_.sbt._, _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.sbt.plugins.Giter8TemplatePlugin, _root_.scala.xml.{TopScope=>SUB:DOLLARscope}" sbt.resolvers="https://repo1.maven.org/maven2/|maven|public, file:/Users/pavelkachalouski/.sbt/preloaded/|maven|local-preloaded, /Users/pavelkachalouski/.ivy2/cache|ivy|Local cache" type="SBT_MODULE" version="4">
|
<module external.linked.project.id="root-build" external.linked.project.path="$MODULE_DIR$/../../project" external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" sbt.imports="SUB:DOLLARcba567fe292588d9e13d.`root`, _root_.sbt.Keys._, _root_.sbt._, _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.sbt.plugins.Giter8TemplatePlugin, _root_.sbtassembly.AssemblyPlugin, _root_.sbtassembly.AssemblyPlugin.autoImport._, _root_.scala.xml.{TopScope=>SUB:DOLLARscope}" sbt.resolvers="https://repo1.maven.org/maven2/|maven|public, file:/home/nnm/.sbt/preloaded/|maven|local-preloaded, /home/nnm/.ivy2/cache|ivy|Local cache" type="SBT_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<output url="file://$MODULE_DIR$/../../project/target/idea-classes" />
|
<output url="file://$MODULE_DIR$/../../project/target/idea-classes" />
|
||||||
<output-test url="file://$MODULE_DIR$/../../project/target/idea-test-classes" />
|
<output-test url="file://$MODULE_DIR$/../../project/target/idea-test-classes" />
|
||||||
@@ -14,6 +14,26 @@
|
|||||||
<orderEntry type="module-library">
|
<orderEntry type="module-library">
|
||||||
<library name="sbt: sbt-and-plugins">
|
<library name="sbt: sbt-and-plugins">
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/javax.annotation/jsr250-api/jars/jsr250-api-1.0.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/javax.enterprise/cdi-api/jars/cdi-api-1.0.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/javax.inject/javax.inject/jars/javax.inject-1.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.ant/ant-launcher/jars/ant-launcher-1.9.9.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.ant/ant/jars/ant-1.9.9.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.commons/commons-lang3/jars/commons-lang3-3.4.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.maven/maven-artifact/jars/maven-artifact-3.3.9.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.maven/maven-model/jars/maven-model-3.3.9.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.apache.maven/maven-plugin-api/jars/maven-plugin-api-3.3.9.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.codehaus.plexus/plexus-classworlds/bundles/plexus-classworlds-2.5.2.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.codehaus.plexus/plexus-component-annotations/jars/plexus-component-annotations-1.5.5.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.codehaus.plexus/plexus-utils/jars/plexus-utils-3.0.22.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.eclipse.sisu/org.eclipse.sisu.inject/eclipse-plugins/org.eclipse.sisu.inject-0.3.2.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.eclipse.sisu/org.eclipse.sisu.plexus/eclipse-plugins/org.eclipse.sisu.plexus-0.3.2.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.ow2.asm/asm-commons/jars/asm-commons-6.0.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.ow2.asm/asm-tree/jars/asm-tree-6.0.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.ow2.asm/asm/jars/asm-6.0.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.pantsbuild/jarjar/jars/jarjar-1.6.5.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/org.scalactic/scalactic_2.12/bundles/scalactic_2.12-3.0.1.jar!/" />
|
||||||
|
<root url="jar://$USER_HOME$/.ivy2/cache/scala_2.12/sbt_1.0/com.eed3si9n/sbt-assembly/jars/sbt-assembly-0.14.6.jar!/" />
|
||||||
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/jline.jar!/" />
|
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/jline.jar!/" />
|
||||||
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/scala-compiler.jar!/" />
|
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/scala-compiler.jar!/" />
|
||||||
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/scala-library.jar!/" />
|
<root url="jar://$USER_HOME$/.sbt/boot/scala-2.12.4/lib/scala-library.jar!/" />
|
||||||
|
|||||||
16
.idea/modules/root.iml
generated
16
.idea/modules/root.iml
generated
@@ -15,8 +15,6 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="sbt: com.google.code.gson:gson:2.8.1:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.google.code.gson:gson:2.8.1:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: io.spray:spray-json_2.12:1.3.4:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-http-spray-json_2.12:10.1.1:jar" level="project" />
|
|
||||||
<orderEntry type="module-library">
|
<orderEntry type="module-library">
|
||||||
<library name="sbt: unmanaged-sources-and-docs">
|
<library name="sbt: unmanaged-sources-and-docs">
|
||||||
<CLASSES />
|
<CLASSES />
|
||||||
@@ -32,7 +30,6 @@
|
|||||||
<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.scala-lang.modules:scala-xml_2.12:1.0.6:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-parser-combinators_2.12:1.1.0:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang.modules:scala-parser-combinators_2.12:1.1.0: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: 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:scala-reflect:2.12.6:jar" level="project" />
|
|
||||||
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.6:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.12.6:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.reactivestreams:reactive-streams:1.0.2:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.reactivestreams:reactive-streams:1.0.2:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: org.asynchttpclient:netty-resolver-dns:2.0.33:jar" level="project" />
|
<orderEntry type="library" name="sbt: org.asynchttpclient:netty-resolver-dns:2.0.33:jar" level="project" />
|
||||||
@@ -65,5 +62,18 @@
|
|||||||
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.12:2.5.12:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.12:2.5.12:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe:ssl-config-core_2.12:0.2.3:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.typesafe:ssl-config-core_2.12:0.2.3:jar" level="project" />
|
||||||
<orderEntry type="library" name="sbt: com.typesafe:config:1.3.2:jar" level="project" />
|
<orderEntry type="library" name="sbt: com.typesafe:config:1.3.2:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: com.chuusai:shapeless_2.12:2.3.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-core_2.12:0.9.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-generic_2.12:0.9.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-jawn_2.12:0.9.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-numbers_2.12:0.9.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: io.circe:circe-parser_2.12:0.9.3:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.scala-lang:scala-reflect:2.12.6:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.spire-math:jawn-parser_2.12:0.11.1:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-core_2.12:1.0.1:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-kernel_2.12:1.0.1:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:cats-macros_2.12:1.0.1:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:machinist_2.12:0.6.2:jar" level="project" />
|
||||||
|
<orderEntry type="library" name="sbt: org.typelevel:macro-compat_2.12:1.1.1:jar" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
16
build.sbt
16
build.sbt
@@ -5,15 +5,25 @@ lazy val root = (project in file(".")).
|
|||||||
inThisBuild(List(
|
inThisBuild(List(
|
||||||
organization := "com.example",
|
organization := "com.example",
|
||||||
scalaVersion := "2.12.6",
|
scalaVersion := "2.12.6",
|
||||||
version := "0.1.0-SNAPSHOT"
|
version := "0.1.0-SNAPSHOT",
|
||||||
|
mainClass := Some("eu.xeppaka.bot1.TelegramBotServer")
|
||||||
)),
|
)),
|
||||||
name := "telegram-bot1",
|
name := "telegram-bot1",
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
scalaTest % Test,
|
scalaTest % Test,
|
||||||
akka,
|
akka,
|
||||||
akkaHttp,
|
akkaHttp,
|
||||||
akkaHttpSprayJson,
|
|
||||||
akkaStream,
|
akkaStream,
|
||||||
vkapi
|
vkapi,
|
||||||
|
circleCore,
|
||||||
|
circleGeneric,
|
||||||
|
circleParser
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assemblyMergeStrategy in assembly := {
|
||||||
|
case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.first
|
||||||
|
case x =>
|
||||||
|
val oldStrategy = (assemblyMergeStrategy in assembly).value
|
||||||
|
oldStrategy(x)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ object Dependencies {
|
|||||||
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
|
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
|
||||||
lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.12"
|
lazy val akka = "com.typesafe.akka" %% "akka-actor" % "2.5.12"
|
||||||
lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.1"
|
lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % "10.1.1"
|
||||||
lazy val akkaHttpSprayJson = "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.1"
|
|
||||||
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % "2.5.12"
|
lazy val akkaStream = "com.typesafe.akka" %% "akka-stream" % "2.5.12"
|
||||||
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.9.3"
|
||||||
|
lazy val circleGeneric = "io.circe" %% "circe-generic" % "0.9.3"
|
||||||
|
lazy val circleParser = "io.circe" %% "circe-parser" % "0.9.3"
|
||||||
}
|
}
|
||||||
|
|||||||
1
project/plugins.sbt
Normal file
1
project/plugins.sbt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
akka {
|
akka {
|
||||||
loglevel = "DEBUG"
|
loglevel = "INFO"
|
||||||
}
|
}
|
||||||
BIN
src/main/resources/telegram-bot.p12
Normal file
BIN
src/main/resources/telegram-bot.p12
Normal file
Binary file not shown.
21
src/main/resources/telegram-bot.pem
Normal file
21
src/main/resources/telegram-bot.pem
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDcTCCAlmgAwIBAgIJAKLMfxW4KRHuMA0GCSqGSIb3DQEBCwUAME8xCzAJBgNV
|
||||||
|
BAYTAkNaMQ8wDQYDVQQHDAZQcmFndWUxGjAYBgNVBAoMEVBhdmVsIEthY2hhbG91
|
||||||
|
c2tpMRMwEQYDVQQDDAp4ZXBwYWthLmV1MB4XDTE4MDUxMTE4MjEzOVoXDTI4MDUw
|
||||||
|
ODE4MjEzOVowTzELMAkGA1UEBhMCQ1oxDzANBgNVBAcMBlByYWd1ZTEaMBgGA1UE
|
||||||
|
CgwRUGF2ZWwgS2FjaGFsb3Vza2kxEzARBgNVBAMMCnhlcHBha2EuZXUwggEiMA0G
|
||||||
|
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWwewslKtWJ7yeYrxDuoh5PS7y5/C/
|
||||||
|
0NT6tsAAsh3ZVCqdeNvYj56n8jVob/jJ9EYMqKj7dXLAMopDhtuwdDN/KhW9QVkK
|
||||||
|
zATE1wNxuV3aBVUTJuHHadUYQa7pVevvssAIa1XQ6NvU0pkwdDApylOj1TkA9MFl
|
||||||
|
ZWHWlF0dgrVyGjFxDoWdjm2aLCdRpZCr0giTOfZ5E+OJNALTHcuJO+PRKdEreO1Y
|
||||||
|
VAlT2Sk26f8/iG63C2/t7xWTyJKOjFPxwq3+dkNfJ1AXZ4I7aFDgP7BKogvooYuC
|
||||||
|
BItqog+IRUOoK9Yj24KCUxD+gaI5+tv0j1ov5d0ZAqqaiSql96s2/jyZAgMBAAGj
|
||||||
|
UDBOMB0GA1UdDgQWBBRkOXFj0c0jNdM1nJMRGr0EvfeMuTAfBgNVHSMEGDAWgBRk
|
||||||
|
OXFj0c0jNdM1nJMRGr0EvfeMuTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
|
||||||
|
A4IBAQBtXUOfnKdp1GY5gATTrPdr2s7FyiJvVfx/yeNNNR8ZnZcLjBMulEvXsfNi
|
||||||
|
AL1cEctnSDyT5z2el75nTdAgTFcBZQLsVk9/4ZwBRnfunFqfU5e5X9X9z//yt4Gy
|
||||||
|
Zq9BrMddQE+qwnOclcTDCc0GnyqKbaPiyYFcaXKhdrsflvoJI9tyLwPgjfXADLDF
|
||||||
|
JtjC0gGdbgefDweBUMTF0cpZED9q/J2fKXHurub+3QySvUOvphVFP4dBz2WhdoTe
|
||||||
|
v3lkEVp3I/IUv9qegO0B0o6X+Nnml4/b7HV1PArNceWOA6f57fSL2m6eN6xs4ULJ
|
||||||
|
kfUMloAr25yvmN/tPwm+8Op5ovot
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -5,6 +5,8 @@ import akka.http.scaladsl.model.Uri
|
|||||||
case class BotUri(botId: String) {
|
case class BotUri(botId: String) {
|
||||||
private val baseUri = Uri(s"https://api.telegram.org/bot$botId")
|
private val baseUri = Uri(s"https://api.telegram.org/bot$botId")
|
||||||
|
|
||||||
|
val botUri: Uri = baseUri
|
||||||
|
|
||||||
val getMe: Uri = baseUri.withPath(baseUri.path / "getMe")
|
val getMe: Uri = baseUri.withPath(baseUri.path / "getMe")
|
||||||
|
|
||||||
val setWebhook: Uri = baseUri.withPath(baseUri.path / "setWebhook")
|
val setWebhook: Uri = baseUri.withPath(baseUri.path / "setWebhook")
|
||||||
@@ -12,4 +14,6 @@ case class BotUri(botId: String) {
|
|||||||
val deleteWebhook: Uri = baseUri.withPath(baseUri.path / "deleteWebhook")
|
val deleteWebhook: Uri = baseUri.withPath(baseUri.path / "deleteWebhook")
|
||||||
|
|
||||||
val getWebhookInfo: Uri = baseUri.withPath(baseUri.path / "getWebhookInfo")
|
val getWebhookInfo: Uri = baseUri.withPath(baseUri.path / "getWebhookInfo")
|
||||||
|
|
||||||
|
val sendMessage: Uri = baseUri.withPath(baseUri.path / "sendMessage")
|
||||||
}
|
}
|
||||||
|
|||||||
146
src/main/scala/eu/xeppaka/bot1/CircleSupport.scala
Normal file
146
src/main/scala/eu/xeppaka/bot1/CircleSupport.scala
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Heiko Seeberger
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package eu.xeppaka.bot1
|
||||||
|
|
||||||
|
import akka.http.scaladsl.marshalling.{ Marshaller, ToEntityMarshaller }
|
||||||
|
import akka.http.scaladsl.model.{ ContentType, ContentTypeRange, HttpEntity }
|
||||||
|
import akka.http.scaladsl.model.MediaType
|
||||||
|
import akka.http.scaladsl.model.MediaTypes.`application/json`
|
||||||
|
import akka.http.scaladsl.unmarshalling.{ FromEntityUnmarshaller, Unmarshaller }
|
||||||
|
import akka.util.ByteString
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.syntax.show.toShow
|
||||||
|
import io.circe.{ Decoder, DecodingFailure, Encoder, Json, Printer, jawn }
|
||||||
|
import scala.collection.immutable.Seq
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic to and from JSON marshalling/unmarshalling using an in-scope circe protocol.
|
||||||
|
* The unmarshaller fails fast, throwing the first `Error` encountered.
|
||||||
|
*
|
||||||
|
* To use automatic codec derivation, user needs to import `io.circe.generic.auto._`.
|
||||||
|
*/
|
||||||
|
object FailFastCirceSupport extends FailFastCirceSupport
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic to and from JSON marshalling/unmarshalling using an in-scope circe protocol.
|
||||||
|
* The unmarshaller fails fast, throwing the first `Error` encountered.
|
||||||
|
*
|
||||||
|
* To use automatic codec derivation import `io.circe.generic.auto._`.
|
||||||
|
*/
|
||||||
|
trait FailFastCirceSupport extends BaseCirceSupport with FailFastUnmarshaller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic to and from JSON marshalling/unmarshalling using an in-scope circe protocol.
|
||||||
|
* The unmarshaller accumulates all errors in the exception `Errors`.
|
||||||
|
*
|
||||||
|
* To use automatic codec derivation, user needs to import `io.circe.generic.auto._`.
|
||||||
|
*/
|
||||||
|
object ErrorAccumulatingCirceSupport extends ErrorAccumulatingCirceSupport {
|
||||||
|
final case class DecodingFailures(failures: NonEmptyList[DecodingFailure]) extends Exception {
|
||||||
|
override def getMessage = failures.toList.map(_.show).mkString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic to and from JSON marshalling/unmarshalling using an in-scope circe protocol.
|
||||||
|
* The unmarshaller accumulates all errors in the exception `Errors`.
|
||||||
|
*
|
||||||
|
* To use automatic codec derivation import `io.circe.generic.auto._`.
|
||||||
|
*/
|
||||||
|
trait ErrorAccumulatingCirceSupport extends BaseCirceSupport with ErrorAccumulatingUnmarshaller
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatic to and from JSON marshalling/unmarshalling using an in-scope circe protocol.
|
||||||
|
*/
|
||||||
|
trait BaseCirceSupport {
|
||||||
|
|
||||||
|
def unmarshallerContentTypes: Seq[ContentTypeRange] =
|
||||||
|
mediaTypes.map(ContentTypeRange.apply)
|
||||||
|
|
||||||
|
def mediaTypes: Seq[MediaType.WithFixedCharset] =
|
||||||
|
List(`application/json`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Json` => HTTP entity
|
||||||
|
*
|
||||||
|
* @return marshaller for JSON value
|
||||||
|
*/
|
||||||
|
implicit final def jsonMarshaller(
|
||||||
|
implicit printer: Printer = Printer.noSpaces
|
||||||
|
): ToEntityMarshaller[Json] =
|
||||||
|
Marshaller.oneOf(mediaTypes: _*) { mediaType =>
|
||||||
|
Marshaller.withFixedContentType(ContentType(mediaType)) { json =>
|
||||||
|
HttpEntity(mediaType, printer.pretty(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `A` => HTTP entity
|
||||||
|
*
|
||||||
|
* @tparam A type to encode
|
||||||
|
* @return marshaller for any `A` value
|
||||||
|
*/
|
||||||
|
implicit final def marshaller[A: Encoder](
|
||||||
|
implicit printer: Printer = Printer.noSpaces
|
||||||
|
): ToEntityMarshaller[A] =
|
||||||
|
jsonMarshaller(printer).compose(Encoder[A].apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP entity => `Json`
|
||||||
|
*
|
||||||
|
* @return unmarshaller for `Json`
|
||||||
|
*/
|
||||||
|
implicit final val jsonUnmarshaller: FromEntityUnmarshaller[Json] =
|
||||||
|
Unmarshaller.byteStringUnmarshaller
|
||||||
|
.forContentTypes(unmarshallerContentTypes: _*)
|
||||||
|
.map {
|
||||||
|
case ByteString.empty => throw Unmarshaller.NoContentException
|
||||||
|
case data => jawn.parseByteBuffer(data.asByteBuffer).fold(throw _, identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP entity => `A`
|
||||||
|
*
|
||||||
|
* @tparam A type to decode
|
||||||
|
* @return unmarshaller for `A`
|
||||||
|
*/
|
||||||
|
implicit def unmarshaller[A: Decoder]: FromEntityUnmarshaller[A]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix-in this trait to fail on the first error during unmarshalling.
|
||||||
|
*/
|
||||||
|
trait FailFastUnmarshaller { this: BaseCirceSupport =>
|
||||||
|
|
||||||
|
override implicit final def unmarshaller[A: Decoder]: FromEntityUnmarshaller[A] = {
|
||||||
|
def decode(json: Json) = Decoder[A].decodeJson(json).fold(throw _, identity)
|
||||||
|
jsonUnmarshaller.map(decode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix-in this trait to accumulate all errors during unmarshalling.
|
||||||
|
*/
|
||||||
|
trait ErrorAccumulatingUnmarshaller { this: BaseCirceSupport =>
|
||||||
|
|
||||||
|
override implicit final def unmarshaller[A: Decoder]: FromEntityUnmarshaller[A] = {
|
||||||
|
def decode(json: Json) =
|
||||||
|
Decoder[A]
|
||||||
|
.accumulating(json.hcursor)
|
||||||
|
.fold(failures => throw ErrorAccumulatingCirceSupport.DecodingFailures(failures), identity)
|
||||||
|
jsonUnmarshaller.map(decode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,24 +4,27 @@ import java.io.InputStream
|
|||||||
import java.security.{KeyStore, SecureRandom}
|
import java.security.{KeyStore, SecureRandom}
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.{ActorSystem, Props}
|
||||||
import akka.http.scaladsl.marshalling.Marshal
|
import akka.http.scaladsl.marshalling.Marshal
|
||||||
import akka.http.scaladsl.model._
|
import akka.http.scaladsl.model._
|
||||||
import akka.http.scaladsl.server.Directives._
|
import akka.http.scaladsl.server.Directives._
|
||||||
import akka.http.scaladsl.server.directives.LoggingMagnet
|
|
||||||
import akka.http.scaladsl.server.{Route, RouteResult}
|
import akka.http.scaladsl.server.{Route, RouteResult}
|
||||||
import akka.http.scaladsl.unmarshalling.Unmarshal
|
import akka.http.scaladsl.unmarshalling.Unmarshal
|
||||||
import akka.http.scaladsl.{ConnectionContext, Http, HttpExt, HttpsConnectionContext}
|
import akka.http.scaladsl.{ConnectionContext, Http, HttpExt, HttpsConnectionContext}
|
||||||
import akka.stream.ActorMaterializer
|
import akka.stream.ActorMaterializer
|
||||||
import com.typesafe.sslconfig.akka.AkkaSSLConfig
|
import akka.util.ByteString
|
||||||
|
import eu.xeppaka.bot1.actors.UpdateActor
|
||||||
|
import eu.xeppaka.bot1.actors.UpdateActor.ReceivedUpdate
|
||||||
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
|
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ExecutionContextExecutor, Future}
|
import scala.concurrent.{ExecutionContextExecutor, Future}
|
||||||
import scala.io.StdIn
|
import scala.io.{Source, StdIn}
|
||||||
import scala.util.{Failure, Success}
|
|
||||||
|
|
||||||
class TelegramBotServer(botId: String, port: Int, httpsContext: Option[HttpsConnectionContext])(implicit val actorSystem: ActorSystem) {
|
class TelegramBotServer(botId: String, port: Int, httpsContext: Option[HttpsConnectionContext])(implicit val actorSystem: ActorSystem) {
|
||||||
|
import FailFastCirceSupport._
|
||||||
|
import io.circe.generic.auto._
|
||||||
import eu.xeppaka.bot1.TelegramEntities._
|
import eu.xeppaka.bot1.TelegramEntities._
|
||||||
|
|
||||||
private val botUri = BotUri(botId)
|
private val botUri = BotUri(botId)
|
||||||
@@ -30,11 +33,12 @@ class TelegramBotServer(botId: String, port: Int, httpsContext: Option[HttpsConn
|
|||||||
|
|
||||||
private val http: HttpExt = Http()
|
private val http: HttpExt = Http()
|
||||||
private val hookId = UUID.randomUUID().toString
|
private val hookId = UUID.randomUUID().toString
|
||||||
private val webhookUri = Uri(s"https://xeppaka.eu:8443/$hookId")
|
private val webhookUri = Uri(s"https://xeppaka.eu:$port/$hookId")
|
||||||
private val bindingFuture = http.bindAndHandle(botRoutes(hookId),
|
private val bindingFuture = http.bindAndHandle(botRoutes(hookId),
|
||||||
"127.0.0.1",
|
"pkcloud",
|
||||||
port,
|
port,
|
||||||
connectionContext = httpsContext.getOrElse(http.defaultClientHttpsContext))
|
connectionContext = httpsContext.getOrElse(http.defaultClientHttpsContext))
|
||||||
|
private val updateActor = actorSystem.actorOf(UpdateActor.props(botUri, http))
|
||||||
|
|
||||||
println(s"webhook path: $webhookUri")
|
println(s"webhook path: $webhookUri")
|
||||||
|
|
||||||
@@ -53,25 +57,35 @@ class TelegramBotServer(botId: String, port: Int, httpsContext: Option[HttpsConn
|
|||||||
def botRoutes(hookId: String): Route = {
|
def botRoutes(hookId: String): Route = {
|
||||||
path(hookId) {
|
path(hookId) {
|
||||||
post {
|
post {
|
||||||
logRequestResult(LoggingMagnet(_ => printRequestMethodAndResponseStatus)) {
|
entity(as[Update]) { update =>
|
||||||
onComplete(getBotInfo) {
|
handleWith(processUpdate)
|
||||||
case Success(res) => complete(res.ok.toString)
|
|
||||||
case Failure(ex) => complete(StatusCodes.InternalServerError, "Boooom!")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def processUpdate(update: Update): HttpResponse = {
|
||||||
|
updateActor ! ReceivedUpdate(update)
|
||||||
|
HttpResponse()
|
||||||
|
}
|
||||||
|
|
||||||
def getBotInfo: Future[Response[GetMe]] = {
|
def getBotInfo: Future[Response[GetMe]] = {
|
||||||
http.singleRequest(HttpRequest(uri = botUri.getMe)).flatMap(Unmarshal(_).to[Response[GetMe]])
|
http.singleRequest(HttpRequest(uri = botUri.getMe)).flatMap(Unmarshal(_).to[Response[GetMe]])
|
||||||
}
|
}
|
||||||
|
|
||||||
def setWebhook(): Future[HttpResponse] = {
|
def setWebhook(): Future[HttpResponse] = {
|
||||||
val hook = Webhook(webhookUri.toString())
|
val urlEntity = HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, ByteString(webhookUri.toString()))
|
||||||
Marshal(hook)
|
val urlPart = Multipart.FormData.BodyPart.Strict("url", urlEntity)
|
||||||
.to[MessageEntity]
|
|
||||||
.flatMap(entity => http.singleRequest(HttpRequest(uri = botUri.setWebhook, method = HttpMethods.POST, entity = entity)))
|
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)))
|
||||||
// .flatMap(Unmarshal(_).to[Response[String]])
|
// .flatMap(Unmarshal(_).to[Response[String]])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,14 +111,21 @@ object TelegramBotServer {
|
|||||||
val httpsContext = createHttpsConnectionContext
|
val httpsContext = createHttpsConnectionContext
|
||||||
|
|
||||||
implicit val actorSystem: ActorSystem = ActorSystem("telegram-bot")
|
implicit val actorSystem: ActorSystem = ActorSystem("telegram-bot")
|
||||||
|
implicit val materializer: ActorMaterializer = ActorMaterializer()
|
||||||
implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher
|
implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher
|
||||||
|
|
||||||
val tbs = TelegramBotServer(8443, Some(createHttpsConnectionContext))
|
val tbs = TelegramBotServer(88, Some(createHttpsConnectionContext))
|
||||||
//tbs.setWebhook()
|
|
||||||
|
|
||||||
tbs
|
tbs.setWebhook()
|
||||||
.getWebhookInfo()
|
.flatMap(response => response.entity.toStrict(5 seconds))
|
||||||
.onComplete(println(_))
|
.onComplete(entity => {
|
||||||
|
println(entity.get.data.utf8String)
|
||||||
|
entity.get.discardBytes()
|
||||||
|
})
|
||||||
|
|
||||||
|
// tbs
|
||||||
|
// .getWebhookInfo()
|
||||||
|
// .onComplete(println(_))
|
||||||
|
|
||||||
StdIn.readLine()
|
StdIn.readLine()
|
||||||
|
|
||||||
@@ -113,10 +134,10 @@ object TelegramBotServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def createHttpsConnectionContext: HttpsConnectionContext = {
|
def createHttpsConnectionContext: HttpsConnectionContext = {
|
||||||
val password: Array[Char] = "changeit".toCharArray // do not store passwords in code, read them from somewhere safe!
|
val password: Array[Char] = "".toCharArray // do not store passwords in code, read them from somewhere safe!
|
||||||
|
|
||||||
val ks: KeyStore = KeyStore.getInstance("PKCS12")
|
val ks: KeyStore = KeyStore.getInstance("PKCS12")
|
||||||
val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12")
|
val keystore: InputStream = getClass.getResourceAsStream("/telegram-bot.p12")
|
||||||
|
|
||||||
require(keystore != null, "Keystore required!")
|
require(keystore != null, "Keystore required!")
|
||||||
ks.load(keystore, password)
|
ks.load(keystore, password)
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
package eu.xeppaka.bot1
|
package eu.xeppaka.bot1
|
||||||
|
|
||||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
import com.vk.api.sdk.objects.base.Sticker
|
||||||
import spray.json._
|
import com.vk.api.sdk.objects.video.Video
|
||||||
|
|
||||||
object TelegramEntities extends SprayJsonSupport with DefaultJsonProtocol {
|
object TelegramEntities {
|
||||||
|
|
||||||
case class Response[T](ok: Boolean,
|
case class Response[T](ok: Boolean,
|
||||||
description: Option[String] = None,
|
description: Option[String] = None,
|
||||||
error_code: Option[Int] = None,
|
error_code: Option[Int] = None,
|
||||||
result: T)
|
result: T)
|
||||||
|
|
||||||
object Response {
|
|
||||||
implicit val responseGetMeFormat: RootJsonFormat[Response[GetMe]] = jsonFormat4(Response[GetMe])
|
|
||||||
implicit val responseWebhookInfoFormat: RootJsonFormat[Response[WebhookInfo]] = jsonFormat4(Response[WebhookInfo])
|
|
||||||
implicit val responseStringFormat: RootJsonFormat[Response[String]] = jsonFormat4(Response[String])
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
object GetMe {
|
|
||||||
implicit val getMeFormat: RootJsonFormat[GetMe] = jsonFormat4(GetMe.apply)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class InlineQuery(id: String,
|
case class InlineQuery(id: String,
|
||||||
from: User,
|
from: User,
|
||||||
location: Location,
|
location: Location,
|
||||||
@@ -88,7 +78,96 @@ object TelegramEntities extends SprayJsonSupport with DefaultJsonProtocol {
|
|||||||
username: Option[String] = None,
|
username: Option[String] = None,
|
||||||
language_code: Option[String] = None)
|
language_code: Option[String] = None)
|
||||||
|
|
||||||
case class Message()
|
case class SendMessage(chat_id: Int,
|
||||||
|
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[String] = 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 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 ChatPhoto(small_file_id: String, big_file_id: String)
|
||||||
|
|
||||||
@@ -106,21 +185,45 @@ object TelegramEntities extends SprayJsonSupport with DefaultJsonProtocol {
|
|||||||
sticker_set_name: Option[String] = None,
|
sticker_set_name: Option[String] = None,
|
||||||
can_set_sticker_set: Option[Boolean] = 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 InputFile()
|
||||||
|
|
||||||
object InputFile {
|
case class Venue(location: Location,
|
||||||
implicit val inputFileFormat: RootJsonFormat[InputFile] = jsonFormat0(InputFile.apply)
|
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,
|
case class Webhook(url: String,
|
||||||
certificate: Option[InputFile] = None,
|
certificate: Option[InputFile] = None,
|
||||||
max_connections: Option[Int] = None,
|
max_connections: Option[Int] = None,
|
||||||
allowed_updates: Option[Seq[String]] = None)
|
allowed_updates: Option[Seq[String]] = None)
|
||||||
|
|
||||||
object Webhook {
|
|
||||||
implicit val webHookFormat: RootJsonFormat[Webhook] = jsonFormat4(Webhook.apply)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class WebhookInfo(url: String,
|
case class WebhookInfo(url: String,
|
||||||
has_custom_certificate: Boolean,
|
has_custom_certificate: Boolean,
|
||||||
pending_update_count: Int,
|
pending_update_count: Int,
|
||||||
@@ -128,8 +231,4 @@ object TelegramEntities extends SprayJsonSupport with DefaultJsonProtocol {
|
|||||||
last_error_message: Option[String] = None,
|
last_error_message: Option[String] = None,
|
||||||
max_connections: Option[Int] = None,
|
max_connections: Option[Int] = None,
|
||||||
allowed_updates: Option[Seq[String]] = None)
|
allowed_updates: Option[Seq[String]] = None)
|
||||||
|
|
||||||
object WebhookInfo {
|
|
||||||
implicit val webHookInfoFormat: RootJsonFormat[WebhookInfo] = jsonFormat7(WebhookInfo.apply)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package eu.xeppaka.bot1
|
package eu.xeppaka.bot1.actors
|
||||||
|
|
||||||
import akka.actor.{Actor, Props}
|
import akka.actor.{Actor, Props}
|
||||||
import akka.http.scaladsl.Http
|
import akka.http.scaladsl.Http
|
||||||
import akka.http.scaladsl.model.{HttpEntity, HttpRequest, HttpResponse, StatusCodes}
|
import akka.http.scaladsl.model.{HttpEntity, HttpRequest, HttpResponse, StatusCodes}
|
||||||
import akka.stream.{ActorMaterializer, ActorMaterializerSettings}
|
import akka.stream.{ActorMaterializer, ActorMaterializerSettings}
|
||||||
import eu.xeppaka.bot1.AccessTokenActor.GetToken
|
import eu.xeppaka.bot1.actors.AccessTokenActor.GetToken
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package eu.xeppaka.bot1
|
package eu.xeppaka.bot1.actors
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
42
src/main/scala/eu/xeppaka/bot1/actors/UpdateActor.scala
Normal file
42
src/main/scala/eu/xeppaka/bot1/actors/UpdateActor.scala
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package eu.xeppaka.bot1.actors
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorLogging, Props}
|
||||||
|
import akka.http.scaladsl.HttpExt
|
||||||
|
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpMethods, HttpRequest}
|
||||||
|
import akka.util.ByteString
|
||||||
|
import eu.xeppaka.bot1.TelegramEntities.SendMessage
|
||||||
|
import eu.xeppaka.bot1.{BotUri, TelegramEntities}
|
||||||
|
|
||||||
|
class UpdateActor(botUri: BotUri, http: HttpExt) extends Actor with ActorLogging {
|
||||||
|
import UpdateActor.ReceivedUpdate
|
||||||
|
|
||||||
|
override def receive: Receive = {
|
||||||
|
case ReceivedUpdate(update) => processUpdate(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def processUpdate(update: TelegramEntities.Update) = {
|
||||||
|
log.info(s"Received update: $update")
|
||||||
|
if (update.message.isDefined) {
|
||||||
|
processMessage(update.message.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def processMessage(message: TelegramEntities.Message) = {
|
||||||
|
import io.circe._, io.circe.generic.auto._, io.circe.syntax._
|
||||||
|
|
||||||
|
log.info("Received message from: {}", message.from)
|
||||||
|
val sendMessage = SendMessage(message.chat.id, s"Привет, ${message.from.get.first_name}")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object UpdateActor {
|
||||||
|
|
||||||
|
case class ReceivedUpdate(update: TelegramEntities.Update)
|
||||||
|
|
||||||
|
def props(botUri: BotUri, http: HttpExt): Props = Props(new UpdateActor(botUri, http))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,33 +1,37 @@
|
|||||||
package example
|
package example
|
||||||
|
|
||||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
|
||||||
import akka.http.scaladsl.unmarshalling.Unmarshal
|
import akka.http.scaladsl.unmarshalling.Unmarshal
|
||||||
import akka.stream.Materializer
|
import akka.stream.Materializer
|
||||||
import akka.util.ByteString
|
import akka.util.ByteString
|
||||||
|
import eu.xeppaka.bot1.TelegramEntities
|
||||||
|
import eu.xeppaka.bot1.TelegramEntities.{Chat, Message, User}
|
||||||
import org.scalatest.FlatSpec
|
import org.scalatest.FlatSpec
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import spray.json._
|
|
||||||
|
|
||||||
case class Text(t: Option[String], t1: Option[String])
|
case class Text(t: Option[String], t1: Option[String])
|
||||||
|
|
||||||
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
|
class MarshalSpec extends FlatSpec {
|
||||||
implicit val textFormat = jsonFormat2(Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarshalSpec extends FlatSpec with JsonSupport {
|
"Circle marshal/unmarshal" should "work" in {
|
||||||
|
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
|
||||||
|
case class Large2(p1: Int, p2: Int, p3: Int, p4: Int, p5: Int, p6: Int, p7: Int, p8: Int, p9: Int, p10: Int, p11: Int, p12: Int, p13: Int, p14: Int, p15: Int, p16: Int, p17: Int, p18: Int, p19: Int, p20: Int, p21: Int, p22: Int, p23: Int)
|
||||||
|
|
||||||
"Marshal to MessageEntity" should "work" in {
|
val f = Large2(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)
|
||||||
implicit val mat: Materializer = null
|
|
||||||
val body =
|
val json = f.asJson.noSpaces
|
||||||
"""
|
println(json)
|
||||||
|{"t1": "some text value"}
|
}
|
||||||
""".stripMargin
|
|
||||||
// val bytes = ByteString(body)
|
"Telegram message marshal/unmarshal" should "work" in {
|
||||||
val f = Unmarshal(body).to[Text]
|
import io.circe._, io.circe.generic.auto._, io.circe.syntax._
|
||||||
val entity = Await.result(f, 1 second)
|
|
||||||
println(entity)
|
implicit val printer: Printer = Printer.noSpaces.copy(dropNullValues = true)
|
||||||
|
val c = Chat(555666, "userChat")
|
||||||
|
val m = Message(message_id = 111222, from = None, date = 111333, chat = c)
|
||||||
|
val json = printer.pretty(m.asJson)
|
||||||
|
println(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user