Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/*.iml
/kotlin-js-store/
/.kotlin/
/docs/
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ kotlin {
dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.html)
implementation(libs.ktor.openapi.schema)
}
}

Expand Down
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@ ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref =
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors", version.ref = "ktor" }
ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
ktor-server-caching-headers = { group = "io.ktor", name = "ktor-server-caching-headers", version.ref = "ktor" }
ktor-server-compression = { group = "io.ktor", name = "ktor-server-compression", version.ref = "ktor" }
ktor-server-openapi = { group = "io.ktor", name = "ktor-server-openapi", version.ref = "ktor" }
ktor-server-routing-openapi = { group = "io.ktor", name = "ktor-server-routing-openapi", version.ref = "ktor" }
ktor-server-swagger = { group = "io.ktor", name = "ktor-server-swagger", version.ref = "ktor" }
ktor-network = { group = "io.ktor", name = "ktor-network", version.ref = "ktor" }
ktor-openapi-schema = { group = "io.ktor", name = "ktor-openapi-schema", version.ref = "ktor" }
ktor-redoc = { group = "io.github.smiley4", name = "ktor-redoc", version = "5.4.0" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version = "1.5.28" }

zKtlint = { group = "com.pinterest.ktlint", name = "ktlint-cli", version.ref = "ktlint"}

[bundles]
ktor-server = [ "ktor-server-core", "ktor-server-netty", "ktor-server-websockets", "ktor-server-caching-headers", "ktor-server-compression" ]
ktor-server = [ "ktor-server-core", "ktor-server-netty", "ktor-server-cors", "ktor-server-websockets", "ktor-server-caching-headers", "ktor-server-compression", "ktor-server-openapi", "ktor-server-routing-openapi", "ktor-server-swagger", "ktor-redoc" ]
ktor-client = [ "ktor-client-core", "ktor-client-okhttp" ]
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ The result can easily be saved back again, allowing for a smooth editing experie

For more information, see [the wiki](https://github.com/KyoriPowered/adventure-webui/wiki/Editor-API).

For your convenience, we host the API documentation in [redoc](https://webui.advntr.dev/api/redoc), [swagger](https://webui.advntr.dev/api/swagger) or [raw openapi json](https://webui.advntr.dev/api/docs.json) formats.

### Deployment

To run the server, type `./gradlew run -PisDevelopment`.
To run the server, type `./gradlew jvmRun -PisDevelopment`.
This will create a server running at `http://localhost:8080`.

For production usage, simply remove the development flag from the run task.
Expand Down
11 changes: 11 additions & 0 deletions src/commonMain/kotlin/net/kyori/adventure/webui/BuildInfo.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package net.kyori.adventure.webui

import io.ktor.openapi.JsonSchema
import kotlinx.serialization.Serializable

@JsonSchema.Title("BuildInfo")
@JsonSchema.Description("Information about the build of the server")
@Serializable
public data class BuildInfo(
@JsonSchema.Description("The time the server started")
@JsonSchema.Example("\"2021-08-22T19:20:00Z\"")
public val startedAt: String,
@JsonSchema.Description("The version of Adventure used by the server")
@JsonSchema.Example("4.10.0-SNAPSHOT")
public val version: String,
@JsonSchema.Description("The commit hash of code in use by the server")
@JsonSchema.Example("9f43339123b0ad37cfe210b6562e39b9a3ccf7c7")
public val commit: String,
@JsonSchema.Description("The URL of the Bytebin instance used by the server")
@JsonSchema.Example("\"https://bytebin.lucko.me\"")
public val bytebinInstance: String
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.kyori.adventure.webui.websocket

import io.ktor.openapi.JsonSchema
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
Expand All @@ -16,21 +17,40 @@ public data class Call(

@Serializable
@SerialName("placeholders")
@JsonSchema.Description("Placeholders to use when rendering the message.")
public data class Placeholders(
@JsonSchema.Description("String placeholders to replace in the message.")
@JsonSchema.Example("{\"test\": \"<red>TEST\"}")
public val stringPlaceholders: Map<String, String>? = null,
@JsonSchema.Example("{}")
public val componentPlaceholders: Map<String, JsonObject>? = null
) : Packet

@Serializable
@JsonSchema.Title("Combined")
@JsonSchema.Description("Multi purpose request object")
public data class Combined(
@JsonSchema.Description("MiniMessage String")
@JsonSchema.Example("\"Hello, <red>world!</red>\"")
public val miniMessage: String? = null,
@JsonSchema.Description("Placeholders")
public val placeholders: Placeholders? = null,
@JsonSchema.Description("The background to render the message on.")
@JsonSchema.Example("stone")
public val background: String? = null,
@JsonSchema.Description("The mode to render the message in.")
@JsonSchema.Example("chat_open")
public val mode: String? = null
)

@Serializable
@JsonSchema.Title("InGamePreview")
@JsonSchema.Description("Request to preview a message in-game.")
public data class InGamePreview(
@JsonSchema.Description("The message to render the message in-game.")
@JsonSchema.Example("\"Hello, <red>world!</red>\"")
public val miniMessage: String? = null,
@JsonSchema.Description("A random key to use as the hostname")
@JsonSchema.Example("abc123")
public val key: String? = null
)
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package net.kyori.adventure.webui.websocket

import io.ktor.openapi.JsonSchema
import kotlinx.serialization.Serializable

/** The server -> client response. */
@Serializable public data class Response(public val parseResult: ParseResult? = null)
@Serializable
@JsonSchema.Title("Response")
@JsonSchema.Description("A parse response.")
public data class Response(public val parseResult: ParseResult? = null)

/** The result of a parse. */
@Serializable
@JsonSchema.Title("ParseResult")
public data class ParseResult(
/** If the parse was a success. */
@JsonSchema.Description("If the parse was a success.")
public val success: Boolean,
/** The result of the conversion, only if it was a [success]. */
@JsonSchema.Description("The result of the conversion, only if it was a success.")
public val dom: String? = null,
/** The error message, if it wasn't a [success]. */
@JsonSchema.Description("The error message, if it wasn't a success.")
public val errorMessage: String? = null
)
16 changes: 16 additions & 0 deletions src/commonMain/resources/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,22 @@ <h3 itemprop="name">How do I preview my MiniMessage text in-game?</h3>
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question" class="mb-4">
<h3 itemprop="name">Is MiniMessage Viewer free to use?</h3>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
Yes, MiniMessage Viewer is completely free to use. You can access it online at <a href="https://webui.advntr.dev/">https://webui.advntr.dev/</a> without any cost.
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question" class="mb-4">
<h3 itemprop="name">Is there an api available for MiniMessage Viewer?</h3>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
Yes! You can check out the api documentation using <a href="/api/redoc">redoc</a>, <a href="/api/swagger">swagger</a> or directly access the OpenAPI spec in JSON format <a href="/api/docs.json">here</a>.
</div>
</div>
</div>
</div>
</section>

Expand Down
50 changes: 49 additions & 1 deletion src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/Application.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
package net.kyori.adventure.webui.jvm

import io.github.smiley4.ktorredoc.redoc
import io.ktor.http.CacheControl
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.content.CachingOptions
import io.ktor.openapi.OpenApiDoc
import io.ktor.openapi.OpenApiInfo
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.compression.Compression
import io.ktor.server.plugins.compression.deflate
import io.ktor.server.plugins.compression.gzip
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.plugins.openapi.openAPI
import io.ktor.server.plugins.swagger.swaggerUI
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.openapi.OpenApiDocSource
import io.ktor.server.routing.openapi.OperationDescribeAttributeKey
import io.ktor.server.routing.openapi.plus
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import io.ktor.server.routing.routingRoot
import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.pingPeriod
import io.ktor.server.websocket.timeout
import io.ktor.websocket.WebSocketDeflateExtension
import kotlin.time.Duration
import kotlinx.serialization.json.Json
import kotlin.time.Duration.Companion.seconds

public fun Application.main() {
Expand Down Expand Up @@ -43,11 +58,44 @@ public fun Application.main() {
extensions { install(WebSocketDeflateExtension) }
}

install(CORS) {
anyHost()
allowHeader(HttpHeaders.ContentType)
}

routing {
// enable trace routing if in dev mode
if (developmentMode) {
trace { route -> this@main.log.debug(route.buildText()) }
}

// api docs
val oaInfo = OpenApiInfo(
"Adventure web API",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking, but is this the proper name? we call it adventure-webui and minimessage viewer elsewhere.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not that I would have a better name, adventure-webui api sounds dum too, lol

"Adventure ${getConfigString("miniMessageVersion")}",
description = "OpenAPI documentation for the Adventure web API.",
contact = OpenApiInfo.Contact("PaperMC Discord", "https://discord.gg/papermc", ""),
license = OpenApiInfo.License("The MIT License", "https://github.com/PaperMC/adventure-webui/blob/main/license.txt", "MIT"),
)
val oaSource = OpenApiDocSource.Routing {
routingRoot.descendants().filterNot { it.attributes.getOrNull(OperationDescribeAttributeKey).isNullOrEmpty() }
}
get("/api/docs.json") {
call.respondText(
Json.encodeToString(OpenApiDoc(info = oaInfo) + oaSource.routes(this@main)),
ContentType.Application.Json,
)
}
route("/api/redoc") {
redoc("/api/docs.json") {
pageTitle = "Adventure web API docs"
}
}
swaggerUI("/api/swagger") {
info = oaInfo
source = oaSource
faviconLocation = "/favicon-32x32.png"
}
}
}

Expand Down
Loading