micromessage

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 9 Imported by: 0

README

micromessage

A MiniMessage parser and renderer for Go, targeting Minekube's component model (go.minekube.com/common/minecraft/component).

msg, err := micromessage.Deserialize("<gradient:aqua:blue>Welcome</gradient> <red><bold>friend</bold></red>!")
if err != nil {
    return err
}
return player.SendMessage(msg)

Installation

go get github.com/Hoppou-Hangout/micromessage

Usage

package main

import (
    "github.com/Hoppou-Hangout/micromessage"
)

func main() {
    msg, err := micromessage.Deserialize("<red>Hello <bold>world</bold></red>!")
    if err != nil {
        panic(err)
    }
    // msg is a *component.Text from go.minekube.com/common/minecraft/component.
    // Pass it directly to Gate:
    //   player.SendMessage(msg)
}

MustDeserialize is also available for static messages defined at init time, where a parse error should just panic instead of being handled:

var welcomeMsg = micromessage.MustDeserialize("<gradient:gold:yellow>Welcome!</gradient>")

Supported tags

Tag Notes
<red>, <blue>, etc. All 16 legacy named colors, plus <grey>/<dark_grey> British aliases
<#rrggbb> Bare hex color
<color:NAME>, <colour:NAME>, <c:NAME> Explicit color tag, name or hex
<bold>, <b> and <italic>/<i>/<em>, <underlined>/<u>, <strikethrough>/<st>, <obfuscated>/<obf>
<!bold> Negation shorthand, turns a decoration off
<bold:false> Explicit boolean form, same effect as <!bold>
<gradient> Defaults to white to black with no arguments
<gradient:c1:c2:...:cN> Any number of color stops
<gradient:c1:c2:phase> Trailing numeric argument shifts the gradient's starting point
<rainbow>, <rainbow:phase>, <rainbow:!> Hue cycles across the wrapped text; ! reverses direction, phase is in tenths
<transition:c1:c2:...:cN[:phase]> Same args as <gradient>, but (matching real MiniMessage) resolves to a single static color, not a per-character blend
<shadow:NAME_OR_HEX:[alpha]>, <shadow:#RRGGBBAA>, <!shadow> Text shadow color; alpha (0-1) defaults to 0.25
<insert:VALUE> Shift-click insertion text
<font:KEY>, <font:NAMESPACE:KEY> Sets the font resource key (default namespace minecraft)
<click:ACTION:VALUE> run_command, suggest_command, open_url, open_file, suggest_command, change_page, copy_to_clipboard, show_dialog, custom
<hover:show_text:VALUE> VALUE is itself parsed as MiniMessage, so it can carry its own colors/formatting
<hover:show_item:ID[:COUNT[:NBT]]> ID needs quoting if namespaced, e.g. "minecraft:diamond"; bare ID defaults to the minecraft namespace
<hover:show_entity:TYPE:UUID[:NAME]> Same TYPE quoting rule as show_item; NAME is parsed as MiniMessage
<lang:KEY[:with...]> (tr, translate) Translatable component; each with argument is itself parsed as MiniMessage
<lang_or:KEY:FALLBACK[:with...]> (tr_or, translate_or) Same as <lang>, with a client-side fallback string
<reset> Clears all style for the remainder of the current scope; never closes
<br>, <newline> Inserts a literal newline; never has children or a close tag

Not implemented: <key> (keybind), <selector>, <score>, <nbt>/<data> — the underlying go.minekube.com/common component model has no component types for these. <pride>, <sprite>, <head> are also unimplemented (client-rendered visuals, not representable as plain text/color).

API

Deserialize/MustDeserialize take optional Options, mirroring Adventure's MiniMessage.builder() API:

msg, err := micromessage.Deserialize(
    "Hello <name/>, you have <score:42/> points! <red>bold isn't here</red>",
    micromessage.WithTagResolver(micromessage.Placeholder("name", micromessage.Parsed("<gold>Tom</gold>"))),
    micromessage.WithTagResolver(scoreResolver),
)

By default every standard tag is recognized (StandardTags.All(), equivalent to Adventure's MiniMessage.miniMessage()) and anything that doesn't resolve — an unknown tag name, or a built-in tag you've excluded — is rendered as literal text instead of erroring, matching Adventure's default (non-strict) error handling.

Tag resolvers

All tag resolution goes through a TagResolver:

type TagResolver interface {
    ResolveTag(name string, args *ArgumentQueue) (Tag, bool)
}

name is already lower-cased. ArgumentQueue gives access to the tag's raw arguments — Pop()/PopOr(msg)/PeekOr(msg)/Rest()/HasNext(); PopOr/PeekOr abort resolution with an error (surfaced from Deserialize) if a required argument is missing, matching Adventure's Tag.Argument#popOr.

  • Placeholder(name, tag) matches one tag name case-insensitively regardless of arguments — the common case for a single named value.

  • TagResolverFunc wraps a plain function for tags that need their arguments, e.g. <score:42> or <selector:@a>.

  • NewTagResolverBuilder().Resolver(a).Resolver(b).Build() composes several resolvers into one, tried in order — mirrors TagResolver.builder().

  • WithTagResolver adds a resolver on top of the active tag set (the common case: add a placeholder without touching any built-in tag). WithTags replaces the active set — built-in tags not included (directly, or via StandardTags.All()) stop being recognized:

    // Only "<red>"/"<color:...>" are recognized; "<bold>" renders as literal text.
    msg, err := micromessage.Deserialize("<green><bold>Hai",
        micromessage.WithTags(micromessage.StandardTags.Color()))
    

    StandardTags exposes each built-in category individually — Color, Decorations, Click, HoverEvent, Insertion, Font, Shadow, Gradient, Rainbow, Transition, Translatable, Newline, Reset, and All — matching Adventure's StandardTags class.

Tags

A TagResolver returns a Tag, built with exactly one of:

  • Text(value) — literal text, taking the ambient style but never re-parsed. Self-closing by default. Matches Placeholder.unparsed.

  • Parsed(value) — parsed as its own MiniMessage snippet and spliced in, inheriting the ambient style; the same resolvers apply recursively inside it (capped at 64 levels deep, to catch self-referential placeholders). A pragmatic stand-in for Adventure's PreProcess tags — see the doc comment on Parsed for how it differs.

  • ComponentTag(comp) — a pre-built component.Component inserted verbatim. Self-closing by default. Matches Placeholder.component.

  • StylingTag(styles...) — wraps its content, applying style changes built from ColorStyle, DecorationStyle, ClickStyle, HoverStyle, FontStyle, InsertionStyle, ShadowStyle. Matches Tag.styling(...); this is how every built-in styling tag (<color>, <bold>, <click>, ...) is itself implemented.

  • ModifyingTagValue(m) — a custom ModifyingTag (Visit/PostVisit/Apply), for tags that need to see and transform their whole rendered subtree, the way <gradient>/<rainbow> do.

  • DirectiveTag(Reset) — a ParserDirective: the tag behaves exactly like <reset>, closing every currently open tag. Register it under another name with Resolver(name, Reset):

    clearTag := micromessage.Resolver("clear", micromessage.Reset)
    msg, err := micromessage.Deserialize("<red>hello <bold>world<clear>, how are you?",
        micromessage.WithTagResolver(clearTag))
    

Call .SelfClosing() on any Tag to mark it as never taking a close tag or wrapped content (only Text/ComponentTag default to this).

Presets

DefaultPreset, NonInteractablePreset, and FormattedTextPreset mirror Adventure's MiniMessage.Presets — pass SomePreset.Apply() as an Option:

msg, err := micromessage.Deserialize(input, micromessage.NonInteractablePreset.Apply())

NonInteractablePreset drops <click>/<hover>/<insert> from the tag set and strips any interactable style a custom resolver still introduces. FormattedTextPreset additionally drops any non-text component (e.g. a <lang> translation) from the result.

Strict mode and debugging

WithStrict(true) makes an unclosed tag a parse error instead of auto-closing at EOF, matching Builder#strict(true) — tags that simply don't resolve to anything are still rendered as literal text either way. WithDebug(fn) registers a callback for diagnostic messages.

Preprocessors

WithPreprocessor registers a function that rewrites the raw input string before it's lexed and parsed, e.g. to translate legacy &-formatted color codes into MiniMessage tags:

msg, err := micromessage.Deserialize(
    "&cHello &lworld",
    micromessage.WithPreprocessor(func(s string) string {
        return strings.NewReplacer("&c", "<red>", "&l", "<bold>").Replace(s)
    }),
)

Multiple preprocessors run in the order added, each seeing the previous one's output.

Testing

go test ./...

Documentation

Overview

Package micromessage is a MiniMessage (https://docs.advntr.dev/minimessage) parser and renderer for Gate/Minekube's component model (go.minekube.com/common/minecraft/component).

Example

This mirrors how you'd use the library inside a Gate plugin:

msg, err := micromessage.Deserialize("<gradient:aqua:blue>Welcome</gradient> <red>%s</red>!")
if err != nil {
    return err
}
return player.SendMessage(msg) // player is a go.minekube.com/gate proxy.Player

player.SendMessage takes a component.Component (msg component.Component, opts ...command.MessageOption), and *component.Text (what Deserialize returns) implements that interface directly, so no adapting is needed.

package main

import (
	"fmt"

	"github.com/Hoppou-Hangout/micromessage"
)

func main() {
	msg, err := micromessage.Deserialize(`<gradient:aqua:blue>Welcome</gradient> <red><bold>friend</bold></red>!`)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(msg.Children()) > 0)
}
Output:
true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// DefaultPreset contains every standard tag with no restrictions --
	// the same as never applying a preset at all, matching Adventure's
	// MiniMessage.Preset.DEFAULT.
	DefaultPreset = Preset{WithTags(StandardTags.All())}

	// NonInteractablePreset disables click events, hover events, and text
	// insertion (the "<click>"/"<hover>"/"<insert>" tags are not part of its
	// tag set at all, so they render as literal text), and additionally
	// strips any interactable style a custom TagResolver might still
	// introduce, matching Adventure's MiniMessage.Preset.NON_INTERACTABLE.
	NonInteractablePreset = Preset{
		WithTags(NewTagResolverBuilder().
			Resolver(StandardTags.Color()).
			Resolver(StandardTags.Decorations()).
			Resolver(StandardTags.Font()).
			Resolver(StandardTags.Shadow()).
			Resolver(StandardTags.Gradient()).
			Resolver(StandardTags.Rainbow()).
			Resolver(StandardTags.Transition()).
			Resolver(StandardTags.Translatable()).
			Resolver(StandardTags.Newline()).
			Resolver(StandardTags.Reset()).
			Build()),
		withPostProcess(stripInteractable),
	}

	// FormattedTextPreset only allows text components and their formatting
	// (color, shadow, font, decoration) -- no click/hover/insertion tags,
	// and its post-processor drops any non-text component (e.g. a
	// Translation a custom TagResolver produced) from the result, matching
	// Adventure's MiniMessage.Preset.FORMATTED_TEXT.
	FormattedTextPreset = Preset{
		WithTags(NewTagResolverBuilder().
			Resolver(StandardTags.Color()).
			Resolver(StandardTags.Decorations()).
			Resolver(StandardTags.Font()).
			Resolver(StandardTags.Shadow()).
			Resolver(StandardTags.Gradient()).
			Resolver(StandardTags.Rainbow()).
			Resolver(StandardTags.Transition()).
			Resolver(StandardTags.Newline()).
			Resolver(StandardTags.Reset()).
			Build()),
		withPostProcess(formattedTextOnly),
	}
)
View Source
var StandardTags standardTags

StandardTags exposes the built-in tag vocabulary as individually selectable TagResolvers, matching Adventure's StandardTags class. Combine them with TagResolverBuilder (or pass several to WithTags) to enable only a subset -- e.g. WithTags(StandardTags.Color()) allows "<red>" and "<color:...>" but leaves "<bold>" (and everything else) as literal text. StandardTags.All() is the default set used when WithTags is never called.

Functions

func Deserialize

func Deserialize(input string, opts ...Option) (result *c.Text, err error)

Deserialize parses a MiniMessage string into a *component.Text tree compatible with go.minekube.com/common and, by extension, Gate.

By default every standard tag (StandardTags.All()) is recognized and unresolved/invalid tags are rendered as literal text -- matching Adventure's MiniMessage.miniMessage(). Pass Options to customize this: WithTags to restrict/replace the tag vocabulary, WithTagResolver to add placeholders or other dynamic tags, WithPreprocessor to rewrite the raw input before parsing, WithStrict to error on unclosed tags, and WithDebug to receive diagnostics.

func MustDeserialize added in v0.3.1

func MustDeserialize(input string, opts ...Option) *c.Text

MustDeserialize is similar to Deserialize but panics on error.

Types

type ArgumentError added in v0.6.0

type ArgumentError struct{ Message string }

ArgumentError is the panic value PopOr/PeekOr raise on a missing required argument; Deserialize recovers it into a plain error.

func (*ArgumentError) Error added in v0.6.0

func (e *ArgumentError) Error() string

type ArgumentQueue added in v0.6.0

type ArgumentQueue struct {
	// contains filtered or unexported fields
}

ArgumentQueue gives a TagResolver access to a tag's raw arguments, matching Adventure's ArgumentQueue/Tag.Argument.

func (*ArgumentQueue) HasNext added in v0.6.0

func (q *ArgumentQueue) HasNext() bool

HasNext reports whether another argument remains.

func (*ArgumentQueue) PeekOr added in v0.6.0

func (q *ArgumentQueue) PeekOr(errorMessage string) string

PeekOr is like PopOr but does not advance the queue.

func (*ArgumentQueue) Pop added in v0.6.0

func (q *ArgumentQueue) Pop() (value string, ok bool)

Pop returns the next argument and advances the queue, or ok=false if there isn't one.

func (*ArgumentQueue) PopOr added in v0.6.0

func (q *ArgumentQueue) PopOr(errorMessage string) string

PopOr returns the next argument and advances the queue, or panics with a *ArgumentError carrying errorMessage if there isn't one -- matching Adventure's Tag.Argument#popOr, which interrupts tag resolution on missing required arguments. Deserialize recovers this into a normal error.

func (*ArgumentQueue) Remaining added in v0.6.0

func (q *ArgumentQueue) Remaining() int

Remaining returns how many arguments are left.

func (*ArgumentQueue) Rest added in v0.6.0

func (q *ArgumentQueue) Rest() []string

Rest returns every remaining argument and drains the queue.

type Kind added in v0.3.1

type Kind int
const (
	KindText Kind = iota
	KindElement
)

type ModifyingTag added in v0.6.0

type ModifyingTag interface {
	Visit(node *Node)
	PostVisit()
	Apply(current c.Component, depth int) c.Component
}

ModifyingTag is the interface custom Modifying tags implement, matching Adventure's Modifying tag kind (used internally for <gradient> and <rainbow>). Visit is called once per node in the wrapped content, in depth-first order, before any component is produced; PostVisit is called once after the full traversal; then Apply is called once per produced child component, in order, to transform it.

If a Modifying tag carries state across Visit calls, its TagResolver must return a fresh instance per tag occurrence -- state is not reset between uses.

type Node added in v0.3.1

type Node struct {
	Kind Kind

	Text string // set when Kind == KindText

	Name     string // tag name, set when Kind == KindElement
	Args     []string
	Tag      Tag // the Tag this element resolved to, already looked up at parse time
	Children []*Node
}

Node is either a run of text (which may be the exact source text of a tag that didn't resolve to anything) or a resolved tag.

type Option added in v0.6.0

type Option func(*options)

Option configures a Deserialize call.

func WithDebug added in v0.6.0

func WithDebug(fn func(string)) Option

WithDebug registers a callback that receives diagnostic messages about why a tag failed to resolve, matching Adventure's Builder#debug(Consumer<String>).

func WithPreprocessor added in v0.6.0

func WithPreprocessor(p Preprocessor) Option

WithPreprocessor registers a Preprocessor run over the raw input string before parsing. Multiple preprocessors may be passed (or this option repeated); they run in the order added, each seeing the previous one's output.

func WithStrict added in v0.6.0

func WithStrict(strict bool) Option

WithStrict enables strict mode: an unclosed tag is a parse error instead of being auto-closed at EOF, matching Adventure's Builder#strict(true). Tags that simply don't resolve to anything are still rendered as literal text either way.

func WithTagResolver added in v0.6.0

func WithTagResolver(r TagResolver) Option

WithTagResolver registers an additional TagResolver, tried after the active tag set (the standard set, or whatever WithTags supplied). This is the common way to add placeholders/dynamic tags without disabling any built-ins, matching Adventure's builder.editTags(b -> b.resolver(...)).

func WithTags added in v0.6.0

func WithTags(resolver TagResolver) Option

WithTags replaces the standard built-in tag set with resolver, matching Adventure's MiniMessage.builder().tags(resolver). Built-in tags not included in resolver (directly or via StandardTags.All()) stop being recognized and are rendered as literal text. If never called, the default is StandardTags.All().

type ParserDirective added in v0.6.0

type ParserDirective int

ParserDirective is an instruction to the parser rather than something that produces or modifies a component, matching Adventure's ParserDirective.

const (
	// Reset indicates that this tag should close all currently open tags,
	// exactly like <reset>. Registering a resolver for a custom tag name via
	// DirectiveTag(Reset) gives it identical behavior to <reset> under a
	// different name.
	Reset ParserDirective = iota
)

type Preprocessor added in v0.6.0

type Preprocessor func(input string) string

Preprocessor transforms the raw input string before it is lexed and parsed, e.g. to translate legacy '&'-formatted color codes into MiniMessage tags. Preprocessors run in the order passed to Deserialize, each seeing the previous one's output.

type Preset added in v0.6.0

type Preset []Option

Preset is a pre-built bundle of Options, matching Adventure's MiniMessage.Preset. Turn one into an Option with Apply:

msg, err := micromessage.Deserialize(input, micromessage.NonInteractablePreset.Apply())

func (Preset) Apply added in v0.6.0

func (p Preset) Apply() Option

Apply turns the preset into a single Option, so it can be combined with others: Deserialize(input, SomePreset.Apply(), WithTagResolver(...)).

type StyleApplicable added in v0.6.0

type StyleApplicable interface {
	// contains filtered or unexported methods
}

StyleApplicable is one change StylingTag applies to a c.Style, matching Adventure's StyleBuilderApplicable. Build one with ColorStyle, DecorationStyle, ClickStyle, HoverStyle, FontStyle, InsertionStyle, or ShadowStyle.

func ClickStyle added in v0.6.0

func ClickStyle(ev c.ClickEvent) StyleApplicable

func ColorStyle added in v0.6.0

func ColorStyle(col mccolor.Color) StyleApplicable

func DecorationStyle added in v0.6.0

func DecorationStyle(dec c.Decoration, on bool) StyleApplicable

func FontStyle added in v0.6.0

func FontStyle(k key.Key) StyleApplicable

func HoverStyle added in v0.6.0

func HoverStyle(ev c.HoverEvent) StyleApplicable

func InsertionStyle added in v0.6.0

func InsertionStyle(v string) StyleApplicable

func ShadowStyle added in v0.6.0

func ShadowStyle(sc *c.ShadowColor) StyleApplicable

type Tag added in v0.6.0

type Tag struct {
	// contains filtered or unexported fields
}

Tag is what a TagResolver produces for a tag it recognizes. Build one with Text, Parsed, ComponentTag, StylingTag, or ModifyingTag -- matching Adventure's three Tag kinds (PreProcess, Inserting, Modifying) plus ParserDirective for tags like RESET.

func ComponentTag added in v0.6.0

func ComponentTag(comp c.Component) Tag

ComponentTag returns a Tag that inserts comp verbatim, with whatever style comp already carries -- the ambient style at the tag's position is not applied to it. Self-closing by default, matching Adventure's Placeholder.component.

func DirectiveTag added in v0.6.0

func DirectiveTag(d ParserDirective) Tag

DirectiveTag returns a Tag that behaves as a parser directive, e.g. an alternate spelling of <reset>. See ParserDirective.

func ModifyingTagValue added in v0.6.0

func ModifyingTagValue(m ModifyingTag) Tag

ModifyingTagValue returns a Tag backed by a custom ModifyingTag implementation, matching Adventure's Modifying tags (used internally for <gradient> and <rainbow>).

func Parsed added in v0.6.0

func Parsed(value string) Tag

Parsed returns a Tag whose value is parsed as its own MiniMessage snippet and spliced in at the tag's position, inheriting the ambient style. The same TagResolvers apply recursively inside it (capped at maxTagDepth to catch self-reference). This is a pragmatic stand-in for Adventure's PreProcess tags: rather than splicing the raw string back into the input and re-lexing (which needs a resolver-aware, single-pass parser), it is parsed and rendered as an independent sub-document at render time. For all but pathological cases (tag boundaries split across the substitution) the result is the same. Self-closing by default -- it never wraps content of its own, so trailing input after it stays ordinary sibling text.

func StylingTag added in v0.6.0

func StylingTag(styles ...StyleApplicable) Tag

StylingTag returns an Inserting Tag that applies a set of style changes to its wrapped content, matching Adventure's Tag.styling(StyleBuilderApplicable...). Not self-closing: it wraps whatever follows until a matching close tag (or EOF), same as the built-in <color>/<bold>/<click>/... tags.

func Text

func Text(value string) Tag

Text returns a Tag whose value is inserted as literal text, taking on the ambient style at the tag's position but never itself re-parsed as MiniMessage. It is self-closing by default, matching Adventure's Placeholder.unparsed.

func (Tag) SelfClosing added in v0.6.0

func (t Tag) SelfClosing() Tag

SelfClosing marks an Inserting Tag (Text/ComponentTag/StylingTag) as never taking a close tag or wrapping content, even if the input writes it as <tag>...</tag> or leaves it unclosed. Text and ComponentTag are self-closing by default; use this to opt StylingTag/custom tags in too.

type TagResolver added in v0.6.0

type TagResolver interface {
	ResolveTag(name string, args *ArgumentQueue) (tag Tag, ok bool)
}

TagResolver supplies a Tag for a tag occurrence (name + arguments) the parser encounters. Built-in tags (color names, "bold", "gradient", ...) are themselves ordinary TagResolvers -- see StandardTags -- so the active tag vocabulary is entirely determined by which resolvers are in play; see WithTags and WithTagResolver.

Tag names passed to ResolveTag are already lower-cased (a leading "!"/"?"/"#" sigil, if any, is preserved), matching Adventure's case-insensitive tag name resolution.

func Placeholder

func Placeholder(name string, tag Tag) TagResolver

Placeholder returns a TagResolver that matches a single tag name case-insensitively and always resolves to tag, ignoring any arguments -- the common case (a named value substituted wherever <name> appears).

func Resolver added in v0.6.0

func Resolver(name string, d ParserDirective) TagResolver

Resolver returns a TagResolver that matches a single tag name case-insensitively and resolves to a ParserDirective, e.g. a "<clear>" tag that behaves exactly like "<reset>".

type TagResolverBuilder added in v0.6.0

type TagResolverBuilder struct {
	// contains filtered or unexported fields
}

TagResolverBuilder composes multiple TagResolvers into one, tried in the order added, matching Adventure's TagResolver.builder().

func NewTagResolverBuilder added in v0.6.0

func NewTagResolverBuilder() *TagResolverBuilder

NewTagResolverBuilder returns an empty TagResolverBuilder, matching Adventure's TagResolver.builder().

func (*TagResolverBuilder) Build added in v0.6.0

func (b *TagResolverBuilder) Build() TagResolver

Build returns a single TagResolver that tries the most-recently-added resolver first, matching Adventure's TagResolverBuilder (the last resolver added for a given name wins).

func (*TagResolverBuilder) Resolver added in v0.6.0

Resolver appends r to the set of resolvers this builder combines.

type TagResolverFunc added in v0.6.0

type TagResolverFunc func(name string, args *ArgumentQueue) (Tag, bool)

TagResolverFunc adapts a plain function to a TagResolver.

func (TagResolverFunc) ResolveTag added in v0.6.0

func (f TagResolverFunc) ResolveTag(name string, args *ArgumentQueue) (Tag, bool)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL