Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.
Merged
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
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,28 @@ The following tags are valid:
- `<underline>`
- `<strikethrough>`
- Events
- `<hover:'text''>` for hover-able text. Formatting applies to inner text as well
- `<hover:action:'text'>` for hover-able text. Formatting applies to inner text as well. Actions:
- `show_text`: 1 text argument
- `show_item`: item id and optional item count
- `show_entity`: entity type id, entity uuid, and optional name (text component)
- *you can skip type, in this case `show_text` will be used as default.* Like this: `<hover:'text'>Hover!`
- `<click:action:'text'>` for clickable text. Actions are following
- `open_url` - following text needs to start with "https://"
- `run_command` - following text needs to start with "/"
- `suggest_command` - following text needs to start with "/"
- `copy_to_clipboard`
- `show_dialog`
- `custom` - send custom click action. two arguments: `id` and `payload`
- Other
- `<font:'file_name'>` to change font
- `<rainbow>` to make the text after rainbow. (Resets when another color is applied or when <reset> is reached)
- `<transition:#hex1:#hex2:step>` - Color Interpolation, step is float between 0 and 1
- `<reset>` to reset formatting

Some tags (like `<click:custom...>`) have multiple arguments. Those are separated by `:`.
Like this:
- `<click:custom:'my_id':'my_payload'>Click for custom click!!!`
- `<hover:show_item:'minecraft:book':'1'>Hover to see a book (not really)`

In some cases (format and reset) you can use shortened versions
- `<b>` is short of `<bold>`
- `<i>` is short of `<italic>`
Expand Down
26 changes: 15 additions & 11 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import java.net.http.HttpResponse

plugins {
`maven-publish`
kotlin("jvm") version "1.9.22"
kotlin("plugin.serialization") version "1.9.23"
kotlin("jvm") version "2.1.21"
kotlin("plugin.serialization") version "2.1.21"
application
}

Expand All @@ -19,14 +19,18 @@ repositories {

dependencies {
testImplementation(kotlin("test"))
testImplementation("net.kyori:adventure-api:4.18.0")
testImplementation("net.kyori:adventure-text-minimessage:4.18.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("io.github.jglrxavpok.hephaistos:common:2.2.0")
implementation("io.github.jglrxavpok.hephaistos:gson:2.2.0")
compileOnly("net.kyori:adventure-api:4.18.0")

testImplementation("net.kyori:adventure-api:4.21.0")
testImplementation("net.kyori:adventure-text-minimessage:4.21.0")
compileOnly("net.kyori:adventure-api:4.21.0")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")

implementation("com.google.code.gson:gson:2.13.1")

implementation("io.github.jglrxavpok.hephaistos:common:2.6.1")
implementation("io.github.jglrxavpok.hephaistos:gson:2.6.1")
}

tasks.withType<Test> {
Expand Down Expand Up @@ -142,4 +146,4 @@ fun embed(): String {
"attachments": []
}
""".trimIndent()
}
}
20 changes: 0 additions & 20 deletions src/main/kotlin/io/github/dockyardmc/scroll/ClickAction.kt

This file was deleted.

153 changes: 149 additions & 4 deletions src/main/kotlin/io/github/dockyardmc/scroll/ClickEvent.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,154 @@
package io.github.dockyardmc.scroll

import io.github.dockyardmc.scroll.extensions.put
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonClassDiscriminator
import org.jglrxavpok.hephaistos.nbt.NBT
import org.jglrxavpok.hephaistos.nbt.NBTCompound

@Suppress("MemberVisibilityCanBePrivate")
@OptIn(ExperimentalSerializationApi::class)
@Serializable
class ClickEvent(
val action: ClickAction,
val value: String? = null
)
@JsonClassDiscriminator("action")
sealed class ClickEvent {
companion object {
/**
* @throws MissingFieldException if there's a field missing
* @throws UnsupportedOperationException if `action` is not supported
*/
fun fromNbt(nbt: NBTCompound): ClickEvent {
return when (val action = nbt.getString("action")) {
"open_url" -> OpenUrl(nbt.getString("url") ?: throw MissingFieldException("url"))
"open_file" -> OpenFile(nbt.getString("path") ?: throw MissingFieldException("path"))
"run_command" -> RunCommand(nbt.getString("command") ?: throw MissingFieldException("command"))
"suggest_command" -> SuggestCommand(nbt.getString("command") ?: throw MissingFieldException("command"))
"change_page" -> ChangePage(nbt.getInt("page") ?: throw MissingFieldException("page"))
"copy_to_clipboard" -> CopyToClipboard(nbt.getString("value") ?: throw MissingFieldException("value"))
"show_dialog" -> ShowDialog(nbt.getString("dialog") ?: throw MissingFieldException("dialog"))
"custom" -> Custom(
nbt.getString("id") ?: throw MissingFieldException("id"),
nbt.getString("payload") ?: throw MissingFieldException("payload")
)

null -> throw MissingFieldException("action")
else -> throw UnsupportedOperationException("unknown `action`: `$action`")
}
}
}

abstract val action: String

open fun getNbt(): NBTCompound {
return NBT.Compound { builder ->
builder.put("action", action)
}
}

@Serializable
@SerialName("open_url")
class OpenUrl(val url: String) : ClickEvent() {
@Transient
override val action: String = "open_url"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("url", url)
}
}
}

@Serializable
@SerialName("open_file")
class OpenFile(val path: String) : ClickEvent() {
@Transient
override val action: String = "open_file"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("path", path)
}
}
}

@Serializable
@SerialName("run_command")
class RunCommand(val command: String) : ClickEvent() {
@Transient
override val action: String = "run_command"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("command", command)
}
}
}

@Serializable
@SerialName("suggest_command")
class SuggestCommand(val command: String) : ClickEvent() {
@Transient
override val action: String = "suggest_command"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("command", command)
}
}
}

@Serializable
@SerialName("change_page")
class ChangePage(val page: Int) : ClickEvent() {
@Transient
override val action: String = "change_page"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("page", page)
}
}
}

@Serializable
@SerialName("copy_to_clipboard")
class CopyToClipboard(val value: String) : ClickEvent() {
@Transient
override val action: String = "copy_to_clipboard"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("value", value)
}
}
}

@Serializable
@SerialName("show_dialog")
class ShowDialog(val dialog: String) : ClickEvent() {
@Transient
override val action: String = "show_dialog"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("dialog", dialog)
}
}
}

@Serializable
@SerialName("custom")
class Custom(val id: String, val payload: String) : ClickEvent() {
@Transient
override val action: String = "custom"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("id", id)
put("payload", payload)
}
}
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/io/github/dockyardmc/scroll/Component.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ open class Component(
var obfuscated: Boolean? = null,
var font: String? = null,
var insertion: String? = null,
@SerialName("hover_event")
var hoverEvent: HoverEvent? = null,
@SerialName("click_event")
var clickEvent: ClickEvent? = null
) {
companion object {
Expand Down
12 changes: 0 additions & 12 deletions src/main/kotlin/io/github/dockyardmc/scroll/HoverAction.kt

This file was deleted.

93 changes: 89 additions & 4 deletions src/main/kotlin/io/github/dockyardmc/scroll/HoverEvent.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,94 @@
package io.github.dockyardmc.scroll

import io.github.dockyardmc.scroll.extensions.put
import io.github.dockyardmc.scroll.extensions.toComponent
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonClassDiscriminator
import org.jglrxavpok.hephaistos.nbt.NBT
import org.jglrxavpok.hephaistos.nbt.NBTCompound

@Suppress("MemberVisibilityCanBePrivate")
@OptIn(ExperimentalSerializationApi::class)
@Serializable
class HoverEvent(
val action: HoverAction,
val contents: Component? = null
)
@JsonClassDiscriminator("action")
sealed class HoverEvent {
abstract val type: String

open fun getNbt(): NBTCompound {
return NBT.Compound { builder ->
builder.put("action", type)
}
}

companion object {
/**
* @throws MissingFieldException if there's a field missing
* @throws UnsupportedOperationException if `action` is not supported
*/
fun fromNbt(nbt: NBTCompound): HoverEvent {
return when (val action = nbt.getString("action")) {
"show_text" -> ShowText(nbt.getCompound("value")?.toComponent() ?: throw MissingFieldException("value"))
"show_item" -> ShowItem(
nbt.getString("id") ?: throw MissingFieldException("id"),
nbt.getInt("count") ?: throw MissingFieldException("count")
)
"show_entity" -> ShowEntity(
nbt.getString("id") ?: throw MissingFieldException("id"),
nbt.getString("uuid") ?: throw MissingFieldException("uuid"),
nbt.getCompound("name")?.toComponent()
)

null -> throw MissingFieldException("action")
else -> throw UnsupportedOperationException("unknown `action`: `$action`")
}
}
}

@Serializable
@SerialName("show_text")
class ShowText(val value: Component) : HoverEvent() {
constructor(value: String) : this(value.toComponent())

@Transient
override val type: String = "show_text"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("value", value.toNBT())
}
}
}

// TODO: add components (recursive dependency? :( )
@Serializable
@SerialName("show_item")
class ShowItem(val id: String, val count: Int) : HoverEvent() {
@Transient
override val type: String = "show_item"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("id", id)
put("count", count)
}
}
}

@SerialName("show_entity")
class ShowEntity(val id: String, val uuid: String, val name: Component? = null) : HoverEvent() {
@Transient
override val type: String = "show_entity"

override fun getNbt(): NBTCompound {
return super.getNbt().kmodify {
put("id", id)
put("uuid", uuid)
if(name != null)
put("name", name.toNBT())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.dockyardmc.scroll

class MissingFieldException(field: String) :
RuntimeException("failed to parse: field `$field` is missing")
Loading