mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 19:24:53 +08:00
add HTTP server and basic tstickers-api
- add HTTP server backend - with configurable listening port - default is 30179 - cannot disable it yet - with UI service with a 523 image - with simple RESTful API service - add basic tstickers-api - now can only get and output binary content without file-type tagging or converting -
This commit is contained in:
parent
ee47446900
commit
5aa63de2a9
@ -25,7 +25,7 @@
|
|||||||
[![Maven metadata of snapshots][badge_snapshot_img]][badge_snapshot_target]
|
[![Maven metadata of snapshots][badge_snapshot_img]][badge_snapshot_target]
|
||||||
|
|
||||||
[//]: # (**[说明书][book] | [FindInTelegram][tg-account]**)
|
[//]: # (**[说明书][book] | [FindInTelegram][tg-account]**)
|
||||||
[badge_handbook_img]: https://img.shields.io/website?url=https%3A%2F%2Fbook.sukazyo.cc%2Fmorny&up_message=0.8.0.11*PUTIAN&up_color=7b68ee&down_message=%E4%B8%8D%E5%8F%AF%E7%94%A8&down_color=dc143c&label=%E8%AF%B4%E6%98%8E%E4%B9%A6
|
[badge_handbook_img]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fbook.sukazyo.cc%2Fmorny%2Fmain.json&query=%24.target_version&label=%E8%AF%B4%E6%98%8E%E4%B9%A6&color=7b68ee
|
||||||
[badge_handbook_target]: https://book.sukazyo.cc/morny
|
[badge_handbook_target]: https://book.sukazyo.cc/morny
|
||||||
[badge_telegram_img]: https://img.shields.io/website?url=https%3A%2F%2Ft.me%2Fmorny_cono_annie_bot&up_message=%40morny_cono_annie_bot&up_color=28a8ea&down_message=unavailable&down_color=red&logo=telegram&label=Telegram
|
[badge_telegram_img]: https://img.shields.io/website?url=https%3A%2F%2Ft.me%2Fmorny_cono_annie_bot&up_message=%40morny_cono_annie_bot&up_color=28a8ea&down_message=unavailable&down_color=red&logo=telegram&label=Telegram
|
||||||
[badge_telegram_target]: https://t.me/morny_cono_annie_bot
|
[badge_telegram_target]: https://t.me/morny_cono_annie_bot
|
||||||
|
@ -66,7 +66,11 @@ lazy val root = (project in file("."))
|
|||||||
},
|
},
|
||||||
assembly / artifact := (assembly / artifact).value
|
assembly / artifact := (assembly / artifact).value
|
||||||
.withClassifier(Some("fat")),
|
.withClassifier(Some("fat")),
|
||||||
addArtifact(assembly / artifact, assembly),
|
if (MornyProject.publishWithFatJar) {
|
||||||
|
addArtifact(assembly / artifact, assembly)
|
||||||
|
} else {
|
||||||
|
Nil
|
||||||
|
},
|
||||||
if (System.getenv("DOCKER_BUILD") != null) {
|
if (System.getenv("DOCKER_BUILD") != null) {
|
||||||
assembly / assemblyJarName := {
|
assembly / assemblyJarName := {
|
||||||
sLog.value info "environment DOCKER_BUILD checked"
|
sLog.value info "environment DOCKER_BUILD checked"
|
||||||
|
@ -8,13 +8,13 @@ object MornyConfiguration {
|
|||||||
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
|
val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono"
|
||||||
val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
|
val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s"
|
||||||
|
|
||||||
val VERSION = "2.0.0-alpha10"
|
val VERSION = "2.0.0-alpha11"
|
||||||
val VERSION_DELTA: Option[String] = None
|
val VERSION_DELTA: Option[String] = None
|
||||||
val CODENAME = "guanggu"
|
val CODENAME = "guanggu"
|
||||||
|
|
||||||
val SNAPSHOT = true
|
val SNAPSHOT = true
|
||||||
|
|
||||||
val dependencies = Seq(
|
val dependencies: Seq[ModuleID] = Seq(
|
||||||
|
|
||||||
"com.github.spotbugs" % "spotbugs-annotations" % "4.7.3" % Compile,
|
"com.github.spotbugs" % "spotbugs-annotations" % "4.7.3" % Compile,
|
||||||
|
|
||||||
@ -22,15 +22,19 @@ object MornyConfiguration {
|
|||||||
"cc.sukazyo" % "resource-tools" % "0.2.2",
|
"cc.sukazyo" % "resource-tools" % "0.2.2",
|
||||||
|
|
||||||
"com.github.pengrad" % "java-telegram-bot-api" % "6.2.0",
|
"com.github.pengrad" % "java-telegram-bot-api" % "6.2.0",
|
||||||
|
"org.http4s" %% "http4s-dsl" % "0.23.24",
|
||||||
|
"org.http4s" %% "http4s-circe" % "0.23.24",
|
||||||
|
"org.http4s" %% "http4s-netty-server" % "0.5.11",
|
||||||
|
|
||||||
"com.softwaremill.sttp.client3" %% "core" % "3.9.0",
|
"com.softwaremill.sttp.client3" %% "core" % "3.9.0",
|
||||||
"com.softwaremill.sttp.client3" %% "okhttp-backend" % "3.9.0",
|
"com.softwaremill.sttp.client3" %% "okhttp-backend" % "3.9.0",
|
||||||
"com.squareup.okhttp3" % "okhttp" % "4.11.0" % Runtime,
|
"com.squareup.okhttp3" % "okhttp" % "4.11.0" % Runtime,
|
||||||
|
|
||||||
|
"org.typelevel" %% "case-insensitive" % "1.4.0",
|
||||||
"com.google.code.gson" % "gson" % "2.10.1",
|
"com.google.code.gson" % "gson" % "2.10.1",
|
||||||
"io.circe" %% "circe-core" % "0.14.6",
|
"io.circe" %% "circe-core" % "0.14.6",
|
||||||
"io.circe" %% "circe-generic" % "0.14.6",
|
"io.circe" %% "circe-generic" % "0.14.6",
|
||||||
"io.circe" %% "circe-parser" % "0.14.6",
|
"io.circe" %% "circe-parser" % "0.14.6",
|
||||||
"org.jsoup" % "jsoup" % "1.16.2",
|
"org.jsoup" % "jsoup" % "1.16.2",
|
||||||
|
|
||||||
"com.cronutils" % "cron-utils" % "9.2.0",
|
"com.cronutils" % "cron-utils" % "9.2.0",
|
||||||
@ -40,10 +44,10 @@ object MornyConfiguration {
|
|||||||
// - cron-utils
|
// - cron-utils
|
||||||
"org.slf4j" % "slf4j-nop" % "2.0.9" % Runtime,
|
"org.slf4j" % "slf4j-nop" % "2.0.9" % Runtime,
|
||||||
|
|
||||||
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
|
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
|
||||||
"org.scalatest" %% "scalatest-freespec" % "3.2.17" % Test,
|
"org.scalatest" %% "scalatest-freespec" % "3.2.17" % Test,
|
||||||
// for test report
|
// for test report
|
||||||
"com.vladsch.flexmark" % "flexmark" % "0.64.0" % Test,
|
"com.vladsch.flexmark" % "flexmark" % "0.64.0" % Test,
|
||||||
"com.vladsch.flexmark" % "flexmark-profile-pegdown" % "0.64.0" % Test
|
"com.vladsch.flexmark" % "flexmark-profile-pegdown" % "0.64.0" % Test
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
|
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
|
||||||
|
import sbt.*
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
//noinspection TypeAnnotation
|
//noinspection TypeAnnotation
|
||||||
object MornyProject {
|
object MornyProject {
|
||||||
|
|
||||||
val _git_repo = new FileRepositoryBuilder()
|
val _git_repo = new FileRepositoryBuilder()
|
||||||
.setGitDir(new File(".git"))
|
.setGitDir(file(".git"))
|
||||||
.setWorkTree(new File(""))
|
.setWorkTree(file("."))
|
||||||
.readEnvironment()
|
.readEnvironment()
|
||||||
.build()
|
.build()
|
||||||
val _git = new Git(_git_repo)
|
val _git = new Git(_git_repo)
|
||||||
@ -45,6 +44,7 @@ object MornyProject {
|
|||||||
|
|
||||||
val dependencies = MornyConfiguration.dependencies
|
val dependencies = MornyConfiguration.dependencies
|
||||||
|
|
||||||
|
val publishWithFatJar = !version_is_snapshot
|
||||||
def publishTo = MornyConfiguration.publishTo
|
def publishTo = MornyConfiguration.publishTo
|
||||||
val publishCredentials = MornyConfiguration.publishCredentials
|
val publishCredentials = MornyConfiguration.publishCredentials
|
||||||
|
|
||||||
|
BIN
src/main/resources/assets_morny/images/http-sekai-404.png
Normal file
BIN
src/main/resources/assets_morny/images/http-sekai-404.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
BIN
src/main/resources/assets_morny/images/http-sekai-500.png
Normal file
BIN
src/main/resources/assets_morny/images/http-sekai-500.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
BIN
src/main/resources/assets_morny/images/http-sekai-501.png
Normal file
BIN
src/main/resources/assets_morny/images/http-sekai-501.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
BIN
src/main/resources/assets_morny/images/http-sekai-523.png
Normal file
BIN
src/main/resources/assets_morny/images/http-sekai-523.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
@ -4,6 +4,8 @@ import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger}
|
|||||||
import cc.sukazyo.cono.morny.core.MornyCoeur.*
|
import cc.sukazyo.cono.morny.core.MornyCoeur.*
|
||||||
import cc.sukazyo.cono.morny.core.bot.api.{EventListenerManager, MornyCommandManager, MornyQueryManager}
|
import cc.sukazyo.cono.morny.core.bot.api.{EventListenerManager, MornyCommandManager, MornyQueryManager}
|
||||||
import cc.sukazyo.cono.morny.core.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
|
import cc.sukazyo.cono.morny.core.bot.event.{MornyOnInlineQuery, MornyOnTelegramCommand, MornyOnUpdateTimestampOffsetLock}
|
||||||
|
import cc.sukazyo.cono.morny.core.http.api.{HttpServer, MornyHttpServerContext}
|
||||||
|
import cc.sukazyo.cono.morny.core.http.internal.MornyHttpServerContextImpl
|
||||||
import cc.sukazyo.cono.morny.reporter.MornyReport
|
import cc.sukazyo.cono.morny.reporter.MornyReport
|
||||||
import cc.sukazyo.cono.morny.util.schedule.Scheduler
|
import cc.sukazyo.cono.morny.util.schedule.Scheduler
|
||||||
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||||
@ -38,6 +40,7 @@ object MornyCoeur {
|
|||||||
eventManager: EventListenerManager,
|
eventManager: EventListenerManager,
|
||||||
commandManager: MornyCommandManager,
|
commandManager: MornyCommandManager,
|
||||||
queryManager: MornyQueryManager,
|
queryManager: MornyQueryManager,
|
||||||
|
httpServer: MornyHttpServerContext,
|
||||||
givenCxt: GivenContext
|
givenCxt: GivenContext
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,6 +55,7 @@ object MornyCoeur {
|
|||||||
eventManager: EventListenerManager,
|
eventManager: EventListenerManager,
|
||||||
commandManager: MornyCommandManager,
|
commandManager: MornyCommandManager,
|
||||||
queryManager: MornyQueryManager,
|
queryManager: MornyQueryManager,
|
||||||
|
httpServer: MornyHttpServerContext,
|
||||||
givenCxt: GivenContext
|
givenCxt: GivenContext
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,6 +70,7 @@ object MornyCoeur {
|
|||||||
eventManager: EventListenerManager,
|
eventManager: EventListenerManager,
|
||||||
commandManager: MornyCommandManager,
|
commandManager: MornyCommandManager,
|
||||||
queryManager: MornyQueryManager,
|
queryManager: MornyQueryManager,
|
||||||
|
httpServer: MornyHttpServerContext,
|
||||||
givenCxt: GivenContext
|
givenCxt: GivenContext
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,11 +171,14 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
|||||||
val commands: MornyCommandManager = MornyCommandManager()
|
val commands: MornyCommandManager = MornyCommandManager()
|
||||||
val queries: MornyQueryManager = MornyQueryManager()
|
val queries: MornyQueryManager = MornyQueryManager()
|
||||||
|
|
||||||
|
private var _httpServerContext: MornyHttpServerContext = MornyHttpServerContextImpl()
|
||||||
|
|
||||||
// Coeur Initializing Pre Event
|
// Coeur Initializing Pre Event
|
||||||
modules.foreach(it => it.onInitializingPre(OnInitializingPreContext(
|
modules.foreach(it => it.onInitializingPre(OnInitializingPreContext(
|
||||||
externalContext,
|
externalContext,
|
||||||
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
||||||
eventManager, commands, queries,
|
eventManager, commands, queries,
|
||||||
|
_httpServerContext,
|
||||||
initializeContext)))
|
initializeContext)))
|
||||||
|
|
||||||
// register core/api events
|
// register core/api events
|
||||||
@ -199,12 +207,16 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
|||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// register core http api service
|
||||||
|
import cc.sukazyo.cono.morny.core.http.services as http_srv
|
||||||
|
_httpServerContext register4API http_srv.Ping()
|
||||||
|
|
||||||
// Coeur Initializing Event
|
// Coeur Initializing Event
|
||||||
modules.foreach(it => it.onInitializing(OnInitializingContext(
|
modules.foreach(it => it.onInitializing(OnInitializingContext(
|
||||||
externalContext,
|
externalContext,
|
||||||
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
||||||
eventManager, commands, queries,
|
eventManager, commands, queries,
|
||||||
|
_httpServerContext,
|
||||||
initializeContext)))
|
initializeContext)))
|
||||||
|
|
||||||
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
|
val watchDog: WatchDog = WatchDog("watch-dog", 1000, 1500, { (consumed, _) =>
|
||||||
@ -221,6 +233,7 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
|||||||
externalContext,
|
externalContext,
|
||||||
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
coeurStartTimestamp, account, username, userid, tasks, trusted,
|
||||||
eventManager, commands, queries,
|
eventManager, commands, queries,
|
||||||
|
_httpServerContext,
|
||||||
initializeContext)))
|
initializeContext)))
|
||||||
|
|
||||||
///>>> BLOCK START instance configure & startup stage 2
|
///>>> BLOCK START instance configure & startup stage 2
|
||||||
@ -237,6 +250,9 @@ class MornyCoeur (modules: List[MornyModule])(using val config: MornyConfig)(tes
|
|||||||
modules.foreach(it => it.onStarting(OnStartingContext(
|
modules.foreach(it => it.onStarting(OnStartingContext(
|
||||||
initializeContext)))
|
initializeContext)))
|
||||||
|
|
||||||
|
logger info "start http server"
|
||||||
|
val http: HttpServer = _httpServerContext.start
|
||||||
|
_httpServerContext = null
|
||||||
logger info "start telegram event listening"
|
logger info "start telegram event listening"
|
||||||
import com.pengrad.telegrambot.TelegramException
|
import com.pengrad.telegrambot.TelegramException
|
||||||
account.setUpdatesListener(eventManager, (e: TelegramException) => {
|
account.setUpdatesListener(eventManager, (e: TelegramException) => {
|
||||||
|
@ -103,7 +103,13 @@ public class MornyConfig {
|
|||||||
public final boolean commandLogoutClear;
|
public final boolean commandLogoutClear;
|
||||||
|
|
||||||
/* ======================================= *
|
/* ======================================= *
|
||||||
* system: morny report *
|
* system: http server *
|
||||||
|
* ======================================= */
|
||||||
|
|
||||||
|
public final int httpPort;
|
||||||
|
|
||||||
|
/* ======================================= *
|
||||||
|
* function: reporter *
|
||||||
* ======================================= */
|
* ======================================= */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,11 +170,14 @@ public class MornyConfig {
|
|||||||
this.medicationTimerUseTimezone = prototype.medicationTimerUseTimezone;
|
this.medicationTimerUseTimezone = prototype.medicationTimerUseTimezone;
|
||||||
prototype.medicationNotifyAt.forEach(i -> { if (i < 0 || i > 23) throw new CheckFailure.UnavailableTimeInMedicationNotifyAt(); });
|
prototype.medicationNotifyAt.forEach(i -> { if (i < 0 || i > 23) throw new CheckFailure.UnavailableTimeInMedicationNotifyAt(); });
|
||||||
this.medicationNotifyAt = prototype.medicationNotifyAt;
|
this.medicationNotifyAt = prototype.medicationNotifyAt;
|
||||||
|
if (prototype.httpPort < 0 || prototype.httpPort > 65535) throw new CheckFailure.UnavailableHttpPort();
|
||||||
|
this.httpPort = prototype.httpPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CheckFailure extends RuntimeException {
|
public static class CheckFailure extends RuntimeException {
|
||||||
public static class NullTelegramBotKey extends CheckFailure {}
|
public static class NullTelegramBotKey extends CheckFailure {}
|
||||||
public static class UnavailableTimeInMedicationNotifyAt extends CheckFailure {}
|
public static class UnavailableTimeInMedicationNotifyAt extends CheckFailure {}
|
||||||
|
public static class UnavailableHttpPort extends CheckFailure {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Prototype {
|
public static class Prototype {
|
||||||
@ -193,6 +202,7 @@ public class MornyConfig {
|
|||||||
public long medicationNotifyToChat = -1L;
|
public long medicationNotifyToChat = -1L;
|
||||||
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
|
@Nonnull public ZoneOffset medicationTimerUseTimezone = ZoneOffset.UTC;
|
||||||
@Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
|
@Nonnull public final Set<Integer> medicationNotifyAt = new HashSet<>();
|
||||||
|
public int httpPort = 30179;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,10 @@ object ServerMain {
|
|||||||
case "--trusted-reader-dinner" | "-trsd" => i += 1; config.dinnerTrustedReaders add(args(i) toLong)
|
case "--trusted-reader-dinner" | "-trsd" => i += 1; config.dinnerTrustedReaders add(args(i) toLong)
|
||||||
case "--dinner-chat" | "-chd" => i += 1; config.dinnerChatId = args(i) toLong
|
case "--dinner-chat" | "-chd" => i += 1; config.dinnerChatId = args(i) toLong
|
||||||
|
|
||||||
|
case "--http-listen-port" | "-hp" =>
|
||||||
|
i += 1
|
||||||
|
config.httpPort = args(i) toInt
|
||||||
|
|
||||||
case "--medication-notify-chat" | "-medc" => i += 1; config.medicationNotifyToChat = args(i) toLong
|
case "--medication-notify-chat" | "-medc" => i += 1; config.medicationNotifyToChat = args(i) toLong
|
||||||
case "--medication-notify-timezone" | "-medtz" =>
|
case "--medication-notify-timezone" | "-medtz" =>
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -19,7 +19,8 @@ object ServerModulesLoader {
|
|||||||
morny.medication_timer.ModuleMedicationTimer(),
|
morny.medication_timer.ModuleMedicationTimer(),
|
||||||
morny.morny_misc.ModuleMornyMisc(),
|
morny.morny_misc.ModuleMornyMisc(),
|
||||||
morny.uni_meow.ModuleUniMeow(),
|
morny.uni_meow.ModuleUniMeow(),
|
||||||
morny.reporter.Module()
|
morny.reporter.Module(),
|
||||||
|
morny.stickers_get.Module(),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cc.sukazyo.cono.morny.core.http.api.HttpService4Api
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramImages
|
||||||
|
import org.http4s.{HttpRoutes, MediaType}
|
||||||
|
import org.http4s.dsl.impl./
|
||||||
|
import org.http4s.dsl.io.*
|
||||||
|
import org.http4s.headers.`Content-Type`
|
||||||
|
|
||||||
|
class ServiceUI extends HttpService4Api {
|
||||||
|
|
||||||
|
override lazy val service: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||||
|
case GET -> Root =>
|
||||||
|
NotImplemented(TelegramImages.IMG_501.get)
|
||||||
|
.map(_.withContentType(`Content-Type`(MediaType.image.jpeg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http.api
|
||||||
|
|
||||||
|
import cats.effect.unsafe.IORuntime
|
||||||
|
|
||||||
|
trait HttpServer (using IORuntime) {
|
||||||
|
|
||||||
|
def stop(): Unit
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http.api
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cc.sukazyo.cono.morny.core.Log.exceptionLog
|
||||||
|
import org.http4s.{HttpRoutes, Response}
|
||||||
|
|
||||||
|
trait HttpService4Api {
|
||||||
|
|
||||||
|
lazy val service: HttpRoutes[IO]
|
||||||
|
|
||||||
|
extension (response: Response[IO]) {
|
||||||
|
def setMornyInternalErrorHeader (e: Throwable): Response[IO] =
|
||||||
|
response.setMornyInternalErrorHeader(
|
||||||
|
e.getClass.getSimpleName,
|
||||||
|
e.getMessage,
|
||||||
|
exceptionLog(e)
|
||||||
|
)
|
||||||
|
def setMornyInternalErrorHeader (
|
||||||
|
`Morny-Internal-Error-Type`: String,
|
||||||
|
`Morny-Internal-Error-Message`: String,
|
||||||
|
`Morny-Internal-Error-Detail`: String
|
||||||
|
): Response[IO] =
|
||||||
|
response.withHeaders(
|
||||||
|
"Morny-Internal-Error-Type" -> `Morny-Internal-Error-Type`,
|
||||||
|
"Morny-Internal-Error-Message" -> `Morny-Internal-Error-Message`,
|
||||||
|
"Morny-Internal-Error-Detail" -> `Morny-Internal-Error-Detail`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object HttpService4Api {
|
||||||
|
def apply (_service: HttpRoutes[IO]): HttpService4Api = new HttpService4Api:
|
||||||
|
override lazy val service: HttpRoutes[IO] = _service
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http.api
|
||||||
|
|
||||||
|
trait MornyHttpServerContext {
|
||||||
|
|
||||||
|
def register4API (service: HttpService4Api*): Unit
|
||||||
|
|
||||||
|
def start: HttpServer
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http.internal
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.core.MornyCoeur
|
||||||
|
import cc.sukazyo.cono.morny.core.http.api.{HttpServer, HttpService4Api, MornyHttpServerContext}
|
||||||
|
import cc.sukazyo.cono.morny.core.http.ServiceUI
|
||||||
|
import cc.sukazyo.cono.morny.core.Log.{exceptionLog, logger}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
class MornyHttpServerContextImpl (using coeur: MornyCoeur) extends MornyHttpServerContext {
|
||||||
|
|
||||||
|
private val services_api = mutable.Queue.empty[HttpService4Api]
|
||||||
|
|
||||||
|
private lazy val service_ui = ServiceUI()
|
||||||
|
|
||||||
|
override def register4API (services: HttpService4Api*): Unit =
|
||||||
|
services_api ++= services
|
||||||
|
|
||||||
|
override def start: HttpServer = {
|
||||||
|
import cats.data.OptionT
|
||||||
|
import cats.effect.unsafe.implicits.global
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.implicits.toSemigroupKOps
|
||||||
|
import org.http4s.server.{Router, Server}
|
||||||
|
import org.http4s.server.middleware.{ErrorAction, ErrorHandling}
|
||||||
|
|
||||||
|
val router = Router(
|
||||||
|
"/" -> service_ui.service,
|
||||||
|
"/api" -> services_api.map(_.service).reduce(_ <+> _)
|
||||||
|
)
|
||||||
|
|
||||||
|
def errorHandler (t: Throwable, message: =>String): OptionT[IO, Unit] =
|
||||||
|
OptionT.liftF(IO {
|
||||||
|
logger error
|
||||||
|
s"""Unexpected exception occurred on Morny Http Server :
|
||||||
|
|${exceptionLog(t)}""".stripMargin
|
||||||
|
})
|
||||||
|
val withErrorHandler = ErrorHandling.Recover.total(
|
||||||
|
ErrorAction.log(
|
||||||
|
router,
|
||||||
|
messageFailureLogAction = errorHandler,
|
||||||
|
serviceErrorLogAction = errorHandler
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val server = org.http4s.netty.server.NettyServerBuilder[IO]
|
||||||
|
.bindHttp(coeur.config.httpPort, "0.0.0.0")
|
||||||
|
.withHttpApp(withErrorHandler.orNotFound)
|
||||||
|
.resource
|
||||||
|
val (_server, _shutdown_io) = server.allocated.unsafeRunSync() match
|
||||||
|
case (_1, _2) => (_1, _2)
|
||||||
|
logger notice s"Morny HTTP Server started at ${_server.baseUri}"
|
||||||
|
|
||||||
|
new HttpServer(using global):
|
||||||
|
val server: Server = _server
|
||||||
|
private val shutdown_io = _shutdown_io
|
||||||
|
override def stop (): Unit = shutdown_io.unsafeRunSync()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package cc.sukazyo.cono.morny.core.http.services
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cc.sukazyo.cono.morny.core.http.api.HttpService4Api
|
||||||
|
import org.http4s.{HttpRoutes, Response}
|
||||||
|
import org.http4s.circe.jsonEncoder
|
||||||
|
import org.http4s.dsl.io.*
|
||||||
|
|
||||||
|
class Ping extends HttpService4Api {
|
||||||
|
|
||||||
|
case class PingResult (
|
||||||
|
pong: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
override lazy val service: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||||
|
case GET -> Root / "ping" =>
|
||||||
|
import io.circe.generic.auto.*
|
||||||
|
import io.circe.syntax.*
|
||||||
|
Ok(PingResult().asJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,5 +31,9 @@ object TelegramImages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val IMG_ABOUT: AssetsFileImage = AssetsFileImage("images/featured-image@0.5x.jpg")
|
val IMG_ABOUT: AssetsFileImage = AssetsFileImage("images/featured-image@0.5x.jpg")
|
||||||
|
val IMG_404: AssetsFileImage = AssetsFileImage("images/http-sekai-404.png")
|
||||||
|
val IMG_500: AssetsFileImage = AssetsFileImage("images/http-sekai-500.png")
|
||||||
|
val IMG_501: AssetsFileImage = AssetsFileImage("images/http-sekai-501.png")
|
||||||
|
val IMG_523: AssetsFileImage = AssetsFileImage("images/http-sekai-523.png")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package cc.sukazyo.cono.morny.stickers_get
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.core.internal.MornyInternalModule
|
||||||
|
import cc.sukazyo.cono.morny.core.MornyCoeur
|
||||||
|
import cc.sukazyo.cono.morny.stickers_get.http.StickerService
|
||||||
|
|
||||||
|
class Module extends MornyInternalModule {
|
||||||
|
|
||||||
|
override val id: String = "morny.stickers-get"
|
||||||
|
override val name: String = "Morny Can Get and Provide Stickers"
|
||||||
|
override val description: String | Null =
|
||||||
|
// language=markdown
|
||||||
|
"""Make Morny as a Telegram Stickers API.
|
||||||
|
|
|
||||||
|
|This module handles `/api/sticker` route that you can get a sticker
|
||||||
|
|by a sticker ID via HTTP request.
|
||||||
|
|
|
||||||
|
|original idea is: https://github.com/tjhorner/tstickers-api
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
override def onInitializing (using MornyCoeur)(cxt: MornyCoeur.OnInitializingContext): Unit = {
|
||||||
|
import cxt.*
|
||||||
|
|
||||||
|
httpServer register4API StickerService()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package cc.sukazyo.cono.morny.stickers_get.http
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cc.sukazyo.cono.morny.core.http.api.HttpService4Api
|
||||||
|
import cc.sukazyo.cono.morny.core.MornyCoeur
|
||||||
|
import cc.sukazyo.cono.morny.data.TelegramImages
|
||||||
|
import com.pengrad.telegrambot.request.GetFile
|
||||||
|
import org.http4s.{HttpRoutes, MediaType}
|
||||||
|
import org.http4s.dsl.io.*
|
||||||
|
import org.http4s.headers.`Content-Type`
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class StickerService (using coeur: MornyCoeur) extends HttpService4Api {
|
||||||
|
|
||||||
|
override lazy val service: HttpRoutes[IO] = HttpRoutes.of {
|
||||||
|
|
||||||
|
case GET -> Root / "sticker" / "id" / id =>
|
||||||
|
try {
|
||||||
|
val response = coeur.account execute GetFile(id)
|
||||||
|
if response.isOk then
|
||||||
|
try {
|
||||||
|
val file = coeur.account getFileContent response.file
|
||||||
|
Ok(file)
|
||||||
|
} catch {
|
||||||
|
case e: IOException =>
|
||||||
|
ServiceUnavailable(
|
||||||
|
TelegramImages.IMG_523.get,
|
||||||
|
`Content-Type`(MediaType.image.png),
|
||||||
|
).map(_.setMornyInternalErrorHeader(e))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
NotFound(
|
||||||
|
TelegramImages.IMG_404.get,
|
||||||
|
`Content-Type`(MediaType.image.png),
|
||||||
|
).map(_.setMornyInternalErrorHeader(
|
||||||
|
"_telegram_api",
|
||||||
|
response.errorCode.toString,
|
||||||
|
response.description,
|
||||||
|
))
|
||||||
|
} catch
|
||||||
|
case io: IOException =>
|
||||||
|
ServiceUnavailable(
|
||||||
|
TelegramImages.IMG_523.get,
|
||||||
|
`Content-Type`(MediaType.image.png),
|
||||||
|
).map(_.setMornyInternalErrorHeader(io))
|
||||||
|
case e: Throwable =>
|
||||||
|
InternalServerError(
|
||||||
|
TelegramImages.IMG_500.get,
|
||||||
|
`Content-Type`(MediaType.image.png),
|
||||||
|
).map(_.setMornyInternalErrorHeader(e))
|
||||||
|
|
||||||
|
case GET -> Root / "sticker" =>
|
||||||
|
NotFound("not found")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package cc.sukazyo.cono.morny
|
package cc.sukazyo.cono.morny
|
||||||
|
|
||||||
|
import cc.sukazyo.cono.morny.core.ServerMain
|
||||||
import cc.sukazyo.cono.morny.util.UniversalCommand
|
import cc.sukazyo.cono.morny.util.UniversalCommand
|
||||||
|
|
||||||
import scala.io.StdIn
|
import scala.io.StdIn
|
||||||
|
Loading…
Reference in New Issue
Block a user