Skip to content

Commit 4f2debc

Browse files
authored
fix: adopt modern Common text components
Bump go.minekube.com/common to v0.4.0 and allow Java status/MOTD descriptions to use modern Common components, including object-root descriptions.\n\nAlso keeps packet component holders compatible with binary-tag protocols by expanding compact text JSON before NBT conversion.\n\nFixes minekube#810.
1 parent a537f2e commit 4f2debc

14 files changed

Lines changed: 260 additions & 40 deletions

File tree

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ require (
3232
github.com/urfave/cli/v2 v2.27.7
3333
github.com/zyedidia/generic v1.2.1
3434
go.minekube.com/brigodier v0.0.2
35-
go.minekube.com/common v0.3.0
35+
go.minekube.com/common v0.4.0
3636
go.minekube.com/connect v0.6.2
3737
go.minekube.com/geyserlite v0.3.14
3838
go.minekube.com/vialite v0.2.8
@@ -69,7 +69,7 @@ require (
6969
github.com/golang/snappy v0.0.4 // indirect
7070
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
7171
github.com/klauspost/compress v1.18.0 // indirect
72-
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
72+
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
7373
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
7474
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
7575
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
134134
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
135135
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
136136
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
137+
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
138+
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
137139
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
138140
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
139141
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
@@ -250,6 +252,8 @@ go.minekube.com/brigodier v0.0.2 h1:g6VJtyeQr7Y+lAP1EfpM7Cdv5STiJaE2eXv8LRuGDG8=
250252
go.minekube.com/brigodier v0.0.2/go.mod h1:WJf/lyJVTId/phiY6phPW6++qkTjCQ72rbOWqo4XIqc=
251253
go.minekube.com/common v0.3.0 h1:eKqNHFDR3eIclCqViHKP/gzWCYgfRdFpZ8+vnKPrnv8=
252254
go.minekube.com/common v0.3.0/go.mod h1:CKCUOhcDjVWRfn77u3Tp/ECV6l1OQ5WUW5M159pu38M=
255+
go.minekube.com/common v0.4.0 h1:gIeGWtXYh7cIaCOSEBk1myzN211MCRXAuSZZ2TqStMY=
256+
go.minekube.com/common v0.4.0/go.mod h1:Nr3bszhOFSBFlglXyqtUmoiLawg+PfzeNEgN00PkK9c=
253257
go.minekube.com/connect v0.6.2 h1:RajPc7YgqygcOviV2g4xetL3J0Wzi8b/lsYXUzIltxE=
254258
go.minekube.com/connect v0.6.2/go.mod h1:28k11I4RyzUfAL3AkOXt3atzjeOFAC8eqbCMwsYKAb0=
255259
go.minekube.com/geyserlite v0.3.14 h1:+cSN9o/b5jEOHeym/uXvt5MEoGnrYloJidrVe9K9pWo=

pkg/edition/java/config/config.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ var DefaultConfig = Config{
8080
Bedrock: bconfig.DefaultBedrockConfig,
8181
}
8282

83-
func defaultMotd() *configutil.TextComponent {
84-
return text("§bA Gate Proxy\n§bVisit ➞ §fgithub.com/minekube/gate")
83+
func defaultMotd() *configutil.Component {
84+
return componentText("§bA Gate Proxy\n§bVisit ➞ §fgithub.com/minekube/gate")
8585
}
8686
func defaultShutdownReason() *configutil.TextComponent {
8787
return text("§cGate proxy is shutting down...\nPlease reconnect in a moment!")
@@ -138,10 +138,10 @@ type Config struct { // TODO use https://github.com/projectdiscovery/yamldoc-go
138138
type (
139139
ForcedHosts map[string][]string // virtualhost:server names
140140
Status struct {
141-
ShowMaxPlayers int `yaml:"showMaxPlayers"`
142-
Motd *configutil.TextComponent `yaml:"motd"`
143-
Favicon favicon.Favicon `yaml:"favicon"`
144-
LogPingRequests bool `yaml:"logPingRequests"`
141+
ShowMaxPlayers int `yaml:"showMaxPlayers"`
142+
Motd *configutil.Component `yaml:"motd"`
143+
Favicon favicon.Favicon `yaml:"favicon"`
144+
LogPingRequests bool `yaml:"logPingRequests"`
145145
}
146146
Query struct {
147147
Enabled bool `yaml:"enabled"`
@@ -328,6 +328,11 @@ func text(s string) *configutil.TextComponent {
328328
version.MinimumVersion.Protocol, s)))
329329
}
330330

331+
func componentText(s string) *configutil.Component {
332+
return &configutil.Component{Value: must(componentutil.ParseComponent(
333+
version.MaximumVersion.Protocol, s))}
334+
}
335+
331336
func must[T any](t T, err error) T {
332337
if err != nil {
333338
panic(err)

pkg/edition/java/config/config_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/stretchr/testify/require"
9+
"go.minekube.com/common/minecraft/component"
910
"gopkg.in/yaml.v3"
1011

1112
bconfig "go.minekube.com/gate/pkg/edition/bedrock/config"
@@ -17,6 +18,16 @@ func Test_texts(t *testing.T) {
1718
require.NotNil(t, defaultShutdownReason())
1819
}
1920

21+
func TestStatusMotdAcceptsObjectRootComponent(t *testing.T) {
22+
var cfg Config
23+
require.NoError(t, yaml.Unmarshal([]byte(`
24+
status:
25+
motd: '{"fallback":"diamond","sprite":"minecraft:item/diamond"}'
26+
`), &cfg))
27+
28+
require.IsType(t, &component.Object{}, cfg.Status.Motd.C())
29+
}
30+
2031
func TestViaConfigValidate(t *testing.T) {
2132
cfg := DefaultConfig
2233
cfg.Servers = map[string]string{"Lobby": "127.0.0.1:25566"}

pkg/edition/java/lite/config/config.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ type (
4141
Strategy Strategy `json:"strategy,omitempty" yaml:"strategy,omitempty"`
4242
}
4343
Status struct {
44-
MOTD *configutil.TextComponent `yaml:"motd,omitempty" json:"motd,omitempty"`
45-
Version ping.Version `yaml:"version,omitempty" json:"version,omitempty"`
46-
Players *ping.Players `json:"players,omitempty" yaml:"players,omitempty"`
47-
Favicon favicon.Favicon `yaml:"favicon,omitempty" json:"favicon,omitempty"`
48-
ModInfo modinfo.ModInfo `yaml:"modInfo,omitempty" json:"modInfo,omitempty"`
44+
MOTD *configutil.Component `yaml:"motd,omitempty" json:"motd,omitempty"`
45+
Version ping.Version `yaml:"version,omitempty" json:"version,omitempty"`
46+
Players *ping.Players `json:"players,omitempty" yaml:"players,omitempty"`
47+
Favicon favicon.Favicon `yaml:"favicon,omitempty" json:"favicon,omitempty"`
48+
ModInfo modinfo.ModInfo `yaml:"modInfo,omitempty" json:"modInfo,omitempty"`
4949
}
5050
)
5151

@@ -54,7 +54,7 @@ func (s *Status) Response(proto.Protocol) (*ping.ServerPing, error) {
5454
return &ping.ServerPing{
5555
Version: s.Version,
5656
Players: s.Players,
57-
Description: s.MOTD.T(),
57+
Description: s.MOTD.C(),
5858
Favicon: s.Favicon,
5959
ModInfo: &s.ModInfo,
6060
}, nil

pkg/edition/java/lite/fallback_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/go-logr/logr/testr"
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11+
"go.minekube.com/common/minecraft/component"
1112
"go.minekube.com/gate/pkg/edition/java/lite/config"
1213
"go.minekube.com/gate/pkg/edition/java/ping"
1314
"go.minekube.com/gate/pkg/edition/java/proto/packet"
@@ -37,9 +38,7 @@ func TestHandleFallbackResponse(t *testing.T) {
3738
})
3839

3940
t.Run("fallback with valid MOTD", func(t *testing.T) {
40-
motd := configutil.TextComponent{
41-
Content: "Server is down for maintenance",
42-
}
41+
motd := configutil.Component{Value: &component.Text{Content: "Server is down for maintenance"}}
4342

4443
route := &config.Route{
4544
Fallback: &config.Status{
@@ -60,9 +59,7 @@ func TestHandleFallbackResponse(t *testing.T) {
6059
})
6160

6261
t.Run("fallback with players info", func(t *testing.T) {
63-
motd := configutil.TextComponent{
64-
Content: "All backends offline",
65-
}
62+
motd := configutil.Component{Value: &component.Text{Content: "All backends offline"}}
6663

6764
route := &config.Route{
6865
Fallback: &config.Status{

pkg/edition/java/lite/integration_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/go-logr/logr/testr"
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11+
"go.minekube.com/common/minecraft/component"
1112
"go.minekube.com/gate/pkg/edition/java/lite/config"
1213
"go.minekube.com/gate/pkg/edition/java/ping"
1314
"go.minekube.com/gate/pkg/edition/java/proto/packet"
@@ -102,9 +103,7 @@ func TestFallbackResponseWithRealRoute(t *testing.T) {
102103
Host: []string{"test.com"},
103104
Backend: []string{"server:25565"},
104105
Fallback: &config.Status{
105-
MOTD: &configutil.TextComponent{
106-
Content: "Maintenance Mode",
107-
},
106+
MOTD: &configutil.Component{Value: &component.Text{Content: "Maintenance Mode"}},
108107
Version: ping.Version{
109108
Name: "Gate Lite",
110109
Protocol: 765,

pkg/edition/java/ping/pong.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"go.minekube.com/common/minecraft/component/codec/legacy"
1111
"go.minekube.com/gate/pkg/edition/java/forge/modinfo"
1212
"go.minekube.com/gate/pkg/edition/java/proto/util"
13+
protoversion "go.minekube.com/gate/pkg/edition/java/proto/version"
1314
"go.minekube.com/gate/pkg/gate/proto"
1415
"go.minekube.com/gate/pkg/util/componentutil"
1516
"go.minekube.com/gate/pkg/util/favicon"
@@ -19,11 +20,11 @@ import (
1920

2021
// ServerPing is a 1.7 and above server list ping response.
2122
type ServerPing struct {
22-
Version Version `json:"version,omitempty" yaml:"version,omitempty"`
23-
Players *Players `json:"players,omitempty" yaml:"players,omitempty"`
24-
Description *component.Text `json:"description" yaml:"description"`
25-
Favicon favicon.Favicon `json:"favicon,omitempty" yaml:"favicon,omitempty"`
26-
ModInfo *modinfo.ModInfo `json:"modinfo,omitempty" yaml:"modinfo,omitempty"`
23+
Version Version `json:"version,omitempty" yaml:"version,omitempty"`
24+
Players *Players `json:"players,omitempty" yaml:"players,omitempty"`
25+
Description component.Component `json:"description" yaml:"description"`
26+
Favicon favicon.Favicon `json:"favicon,omitempty" yaml:"favicon,omitempty"`
27+
ModInfo *modinfo.ModInfo `json:"modinfo,omitempty" yaml:"modinfo,omitempty"`
2728
}
2829

2930
// Make sure ServerPing implements the interfaces at compile time.
@@ -66,7 +67,7 @@ func (p *ServerPing) UnmarshalJSON(data []byte) error {
6667
out.Alias.Description = &component.Text{} // empty component
6768
} else {
6869
var err error
69-
out.Alias.Description, err = componentutil.ParseTextComponent(out.Version.Protocol, string(out.Description))
70+
out.Alias.Description, err = componentutil.ParseComponent(out.Version.Protocol, string(out.Description))
7071
if err != nil {
7172
return fmt.Errorf("error decoding description: %w", err)
7273
}
@@ -89,7 +90,7 @@ func (p *ServerPing) UnmarshalYAML(value *yaml.Node) error {
8990
}
9091

9192
var err error
92-
p.Description, err = componentutil.ParseTextComponent(out.Version.Protocol, out.Description)
93+
p.Description, err = componentutil.ParseComponent(out.Version.Protocol, out.Description)
9394
if err != nil {
9495
return fmt.Errorf("error decoding description: %w", err)
9596
}
@@ -99,22 +100,47 @@ func (p *ServerPing) UnmarshalYAML(value *yaml.Node) error {
99100
}
100101

101102
func (p *ServerPing) MarshalYAML() (any, error) {
102-
b := new(strings.Builder)
103-
err := (&legacy.Legacy{}).Marshal(b, p.Description)
104-
if err != nil {
105-
return nil, fmt.Errorf("error encoding description: %w", err)
103+
description := ""
104+
if text, ok := p.Description.(*component.Text); ok {
105+
b := new(strings.Builder)
106+
if err := (&legacy.Legacy{}).Marshal(b, text); err == nil {
107+
description = b.String()
108+
} else {
109+
description, err = p.marshalDescriptionJSON()
110+
if err != nil {
111+
return nil, err
112+
}
113+
}
114+
} else if p.Description != nil {
115+
var err error
116+
description, err = p.marshalDescriptionJSON()
117+
if err != nil {
118+
return nil, err
119+
}
106120
}
107121

108122
type Alias ServerPing
109123
return &struct {
110124
Description string
111125
*Alias
112126
}{
113-
Description: b.String(),
127+
Description: description,
114128
Alias: (*Alias)(p),
115129
}, nil
116130
}
117131

132+
func (p *ServerPing) marshalDescriptionJSON() (string, error) {
133+
protocol := p.Version.Protocol
134+
if protocol == 0 {
135+
protocol = protoversion.MaximumVersion.Protocol
136+
}
137+
b, err := util.Marshal(protocol, p.Description)
138+
if err != nil {
139+
return "", fmt.Errorf("error encoding description: %w", err)
140+
}
141+
return string(b), nil
142+
}
143+
118144
type Version struct {
119145
Protocol proto.Protocol `json:"protocol,omitempty" yaml:"protocol,omitempty"`
120146
Name string `json:"name,omitempty" yaml:"name,omitempty"`

pkg/edition/java/ping/pong_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package ping
22

33
import (
44
"encoding/json"
5-
"go.minekube.com/gate/pkg/edition/java/proto/util"
65
"testing"
76

87
"github.com/stretchr/testify/require"
98
"go.minekube.com/common/minecraft/component"
9+
"go.minekube.com/common/minecraft/key"
10+
"go.minekube.com/gate/pkg/edition/java/proto/util"
11+
"go.minekube.com/gate/pkg/edition/java/proto/version"
1012
"go.minekube.com/gate/pkg/util/uuid"
13+
"gopkg.in/yaml.v3"
1114
)
1215

1316
func TestServerPing_JSON(t *testing.T) {
@@ -68,3 +71,51 @@ func TestServerPing_UnmarshalJSON_description_string(t *testing.T) {
6871
const exp = `{"extra":[{"color":"yellow","text":"Gate Proxy\n "},{"color":"gold","extra":[{"bold":true,"text":"Hello World"}],"text":""}],"text":" "}`
6972
require.Equal(t, exp, string(js))
7073
}
74+
75+
func TestServerPing_JSON_ModernObjectDescription(t *testing.T) {
76+
blocks := key.New(key.MinecraftNamespace, "blocks")
77+
diamond := key.New(key.MinecraftNamespace, "item/diamond")
78+
description := component.AtlasSprite(blocks, diamond)
79+
description.Fallback = &component.Text{Content: "diamond"}
80+
81+
p := &ServerPing{
82+
Version: Version{
83+
Protocol: version.Minecraft_1_21_11.Protocol,
84+
Name: "1.21.11",
85+
},
86+
Description: description,
87+
}
88+
89+
b, err := json.Marshal(p)
90+
require.NoError(t, err)
91+
require.JSONEq(t, `{
92+
"version":{"protocol":774,"name":"1.21.11"},
93+
"description":{"atlas":"minecraft:blocks","fallback":{"text":"diamond"},"sprite":"minecraft:item/diamond"}
94+
}`, string(b))
95+
96+
var decoded ServerPing
97+
require.NoError(t, json.Unmarshal(b, &decoded))
98+
require.Equal(t, p, &decoded)
99+
}
100+
101+
func TestServerPing_YAML_ModernObjectDescription(t *testing.T) {
102+
blocks := key.New(key.MinecraftNamespace, "blocks")
103+
diamond := key.New(key.MinecraftNamespace, "item/diamond")
104+
description := component.AtlasSprite(blocks, diamond)
105+
description.Fallback = &component.Text{Content: "diamond"}
106+
107+
p := &ServerPing{
108+
Version: Version{
109+
Protocol: version.Minecraft_1_21_11.Protocol,
110+
Name: "1.21.11",
111+
},
112+
Description: description,
113+
}
114+
115+
b, err := yaml.Marshal(p)
116+
require.NoError(t, err)
117+
118+
var decoded map[string]any
119+
require.NoError(t, yaml.Unmarshal(b, &decoded))
120+
require.JSONEq(t, `{"atlas":"minecraft:blocks","fallback":{"text":"diamond"},"sprite":"minecraft:item/diamond"}`, decoded["description"].(string))
121+
}

pkg/edition/java/proto/packet/chat/component_holder.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ func (c *ComponentHolder) AsComponent() (component.Component, error) {
118118
// AsJson returns the component as a JSON raw message.
119119
func (c *ComponentHolder) AsJson() (json.RawMessage, error) {
120120
if len(c.JSON) != 0 {
121-
return c.JSON, nil
121+
var err error
122+
c.JSON, err = componentObjectJSON(c.JSON)
123+
return c.JSON, err
122124
}
123125
if len(c.BinaryTag.Data) != 0 {
124126
var err error
@@ -130,9 +132,26 @@ func (c *ComponentHolder) AsJson() (json.RawMessage, error) {
130132
return nil, err
131133
}
132134
c.JSON, err = util.Marshal(c.Protocol, comp)
135+
if err != nil {
136+
return c.JSON, err
137+
}
138+
c.JSON, err = componentObjectJSON(c.JSON)
133139
return c.JSON, err
134140
}
135141

142+
func componentObjectJSON(j json.RawMessage) (json.RawMessage, error) {
143+
if len(j) == 0 || j[0] != '"' {
144+
return j, nil
145+
}
146+
var text string
147+
if err := json.Unmarshal(j, &text); err != nil {
148+
return nil, err
149+
}
150+
return json.Marshal(struct {
151+
Text string `json:"text"`
152+
}{Text: text})
153+
}
154+
136155
func (c *ComponentHolder) AsJsonOrNil() json.RawMessage {
137156
if c == nil {
138157
return nil

0 commit comments

Comments
 (0)