eternel.eu / minecraftanarchy.com in-game <-> web chat bridge — Folia (Luminol 26.1.2) backend plugin, ChatControl-aware, outbound WebSocket to the eternel-chat relay
Find a file
2026-07-15 01:31:48 +02:00
src/main 1.2.0: broadcast player deaths to the web chat/kill feed 2026-07-03 12:39:30 +02:00
.gitignore EternelChat: Folia in-game<->web chat bridge plugin (ChatControl-aware, outbound WS) 2026-06-28 13:18:14 +00:00
build.gradle.kts 1.2.0: broadcast player deaths to the web chat/kill feed 2026-07-03 12:39:30 +02:00
LICENSE chore: prepare for public release 2026-07-15 01:31:48 +02:00
README.md chore: prepare for public release 2026-07-15 01:31:48 +02:00
settings.gradle.kts EternelChat: Folia in-game<->web chat bridge plugin (ChatControl-aware, outbound WS) 2026-06-28 13:18:14 +00:00

eternel-chat-plugin (EternelChat)

In-game ↔ web chat bridge for eternel.eu / minecraftanarchy.com, built as a backend Folia plugin (Luminol 26.1.2, protocol 776/26.2, Java 25) rather than a mineflayer bot or Velocity plugin — so it can leverage ChatControl for formatting + profanity filtering.

What it does

  • On enable: reads plugins/EternelChat/config.yml (relay URL + shared secret), opens an auto-reconnecting outbound WebSocket (client) to the relay container ws://chat-service:8081/ingest?key=<secret>. No inbound port on the MC server.
  • game → web: reads the final chat line via ChatControl's ChannelPostChatEvent (post-filter, authoritative; ChatControl frequently cancels the Bukkit AsyncChatEvent) at MONITOR. Falls back to AsyncChatEvent if ChatControl is absent. Sends {type:'chat',name,text,ts} to the relay.
  • web → game: on {type:'send',name,text} from the relay, posts "[web] <name>: <text>" in-game through ChatControlAPI.sendMessage(console, "standard", msg) so ChatControl's format + filter + fan-out apply. Clean Adventure broadcast fallback if ChatControl is unavailable.

Folia correctness

  • folia-supported: true in plugin.yml.
  • Chat events are read-only on async chat threads (no world access).
  • All world-touching / player delivery (web→game) runs on GlobalRegionScheduler.
  • WebSocket lifecycle runs on a dedicated daemon scheduler + the Java-WebSocket reader thread — never blocks a region thread.
  • Never uses Bukkit.getScheduler().

Build

Folia API dev.folia:folia-api:1.21.11-R0.1-SNAPSHOT (compileOnly, from PaperMC repo). ChatControl 12.1.7 is a local compileOnly files("libs/ChatControl-12.1.7.jar") (not on Maven Central). Java toolchain 25, options.release = 21 → bytecode 65. org.java-websocket:Java-WebSocket + org.json are shaded and relocated under fr.ekaii.eternelchat.shaded.*.

docker run --rm -v $PWD:/work -v ~/.gradle-cache:/home/gradle/.gradle \
  -w /work gradle:9.6-jdk25-noble gradle --no-daemon clean build
# -> build/libs/EternelChat-<version>.jar

libs/ChatControl-12.1.7.jar is required to build but is not committed (it is the proprietary ChatControl-Red jar). Copy it from the server's plugins/ dir before building.

Architecture

The relay is a separate Coolify Docker-Compose service chat-service (Node + ws) that serves browsers at wss://your-domain/chat and accepts the plugin at /ingest (shared-secret guarded). Rate-limit + EN/FR slur filter for the public widget live in the relay; ChatControl is defence-in-depth.