discord4gate

package module
v0.0.0-...-2c5e60c Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

README

Discord4Gate

Go Reference

A high-performance Discord <> Minecraft bidirectional chat and event bridge extension for the Gate Minecraft proxy.

Connect your Discord community with all backend servers seamlessly with rich player avatars, slash commands, network presence, voice stat counters, and optional companion mod support!


✨ Features

  • 💬 Two-Way Global Chat Bridge:
    • Minecraft ➔ Discord: Real-time message relay with Webhooks (rendering dynamic player skin avatars via UUID) or Bot embeds/text.
    • Discord ➔ Minecraft: Formatted MiniMessage & RGB chat broadcast, clickable attachment links ([Attachment]), hoverable Discord usernames, and reply context.
  • 👥 Network Lifecycle Events:
    • Player Join (**Alice joined the network**)
    • Player Leave (**Alice left the network**)
    • Server Switch (**Alice moved from Lobby to SMP**)
    • Proxy Start & Shutdown announcements
  • 📡 Backend Health Monitoring:
    • Periodically pings backend servers (smp, steelmc, lobby) and alerts Discord when servers restart or crash: ⚠️ [SMP] went offline / restarting ✅ [SMP] is back online
  • 🤖 Discord Presence & Voice Stat Counters:
    • Dynamic bot playing activity (Playing Minecraft with 12 players online).
    • Auto-updating Discord channel topic (12/1000 Online | Uptime: 4h 12m).
    • Live voice channel counters (🎮 Online: 12/1000).
  • Discord Slash Commands (/):
    • /list: Clean list of online players grouped by server.
    • /status: Uptime, memory usage, and backend server ping.
    • /find <player>: Check which server a player is on.
    • /broadcast <message> (Admin): Broadcast to all Minecraft servers.
    • /kick <player> [reason] (Admin): Kick a player from the network.
  • 🔌 Optional Companion Mod / Plugin API:
    • Works 100% standalone on Gate proxy with zero backend mods required.
    • Optionally listens on discord4gate:main, yeplib:main, and mcdiscordchat:main to display backend Advancement and Death embeds when companion mods are present!
  • 🛡️ Privacy & Spam Protection:
    • exclude_servers: Ignore chat from private/admin servers.
    • excluded_commands: Block /login, /register, /msg from being forwarded.
    • Filter @everyone / @here mentions from in-game chat.

📦 Installation

In Pelican / Pterodactyl Panel

Add github.com/andreisugu/gate-discord4gate to your Gate Go Plugins (List):

github.com/andreisugu/gate-simplewhitelist, github.com/andreisugu/gate-hybridforwarding, github.com/andreisugu/gate-bettertab, github.com/andreisugu/gate-smartlimbo, github.com/andreisugu/gate-discord4gate
In Go Code
go get github.com/andreisugu/gate-discord4gate
package main

import (
	"github.com/andreisugu/gate-discord4gate"
	"go.minekube.com/gate/cmd/gate"
	"go.minekube.com/gate/pkg/edition/java/proxy"
)

func main() {
	proxy.Plugins = append(proxy.Plugins,
		discord4gate.Plugin,
	)
	gate.Execute()
}

⚙️ Configuration (config/discord4gate.toml)

exclude_servers = ["picolimbo"]
excluded_commands = ["/msg", "/tell", "/w", "/r", "/login", "/register"]
ping_interval = 15
discord_invite = "https://discord.gg/your-server"

[discord]
token = "YOUR_BOT_TOKEN"
channel = "YOUR_CHANNEL_ID"
show_bot_messages = false
show_attachments_ingame = true
show_activity = true
activity_text = "Minecraft with {amount} players online"
enable_mentions = true
enable_everyone_and_here = false
update_channel_topic_interval = 5
topic = "{players}/{max_players} Online | Uptime: {uptime} | Players: {player_list}"

[discord.webhook]
webhook_url = "https://discord.com/api/webhooks/..."
avatar_url = "https://mc-heads.net/avatar/{username}.png"
webhook_username = "{username} ({server})"

🎮 Commands

Discord Slash Commands
  • /list
  • /status
  • /find <player>
  • /broadcast <message>
  • /kick <player> [reason]
Minecraft In-Game Commands
  • /discord
  • /discord4gate reload
  • /discord4gate status

📄 License

Apache 2.0 © Andrei

Documentation

Index

Constants

View Source
const CurrentConfigVersion = 2

CurrentConfigVersion defines the latest configuration schema version.

Variables

View Source
var Plugin = proxy.Plugin{
	Name: "Discord4Gate",
	Init: func(ctx context.Context, p *proxy.Proxy) error {
		log := logr.FromContextOrDiscard(ctx)
		configPath := filepath.Join("config", "discord4gate.toml")

		store := NewConfigStore(configPath)
		if _, err := store.Load(); err != nil {
			log.Error(err, "Failed to load discord4gate configuration")
			return err
		}
		if store.WasUpgraded() {
			log.Info("Configuration automatically upgraded to latest schema!", "version", CurrentConfigVersion, "backup", configPath+".bak")
		}

		bot := NewDiscordBot(p, store, log)
		_ = bot.Start(ctx)

		companion := NewCompanionHandler(bot, log)
		monitor := NewServerMonitor(p, store, bot, log)
		monitor.Start()

		event.Subscribe(p.Event(), 0, func(e *proxy.PlayerChatEvent) {
			if !e.Allowed() {
				return
			}
			msg := e.Message()
			if store.IsCommandExcluded(msg) {
				return
			}
			srvName := "Server"
			if cs := e.Player().CurrentServer(); cs != nil {
				srvName = cs.Server().ServerInfo().Name()
			}
			if store.IsServerExcluded(srvName) {
				return
			}

			log.Info("Relaying in-game chat to Discord", "player", e.Player().Username(), "server", srvName, "message", msg)
			bot.SendChatMessage(e.Player(), srvName, msg)
		})

		event.Subscribe(p.Event(), 0, func(e *proxy.ServerConnectedEvent) {
			targetSrvName := e.Server().ServerInfo().Name()
			if store.IsServerExcluded(targetSrvName) {
				return
			}

			bot.RecordPlayer(e.Player().ID().String())

			if e.PreviousServer() == nil {
				log.Info("Player joined network", "player", e.Player().Username(), "server", targetSrvName)
				bot.SendJoinMessage(e.Player(), targetSrvName)
			} else {
				prevName := e.PreviousServer().ServerInfo().Name()
				if !strings.EqualFold(prevName, targetSrvName) {
					log.Info("Player switched server", "player", e.Player().Username(), "from", prevName, "to", targetSrvName)
					bot.SendServerSwitchMessage(e.Player(), prevName, targetSrvName)
				}
			}
		})

		event.Subscribe(p.Event(), 0, func(e *proxy.DisconnectEvent) {

			if bot.IsShuttingDown() {
				return
			}

			srvName := "Server"
			if cs := e.Player().CurrentServer(); cs != nil {
				srvName = cs.Server().ServerInfo().Name()
			}
			if store.IsServerExcluded(srvName) {
				return
			}
			log.Info("Player left network", "player", e.Player().Username(), "server", srvName)
			bot.SendLeaveMessage(e.Player(), srvName)
		})

		event.Subscribe(p.Event(), 0, func(e *proxy.PluginMessageEvent) {
			companion.HandlePluginMessage(e)
		})

		event.Subscribe(p.Event(), 0, func(e *proxy.PreShutdownEvent) {
			log.Info("Proxy pre-shutdown: notifying Discord channel...")
			bot.SetShuttingDown(true)
			bot.SendProxyStop()
		})

		event.Subscribe(p.Event(), 0, func(e *proxy.ShutdownEvent) {
			log.Info("Proxy shutdown: disconnecting Discord bot...")
			monitor.Stop()
			bot.Stop()
		})

		RegisterCommands(p, store, bot)

		log.Info("Discord4Gate loaded successfully! (chat bridge, presence, slash commands active)")
		return nil
	},
}

Plugin is the Discord4Gate Discord <> Minecraft chat and event bridge plugin.

Functions

func CleanMinecraftFormatting

func CleanMinecraftFormatting(s string) string

CleanMinecraftFormatting strips Minecraft color tags when sending text to Discord.

func DiscordMarkdownToMiniMessage

func DiscordMarkdownToMiniMessage(content string) string

DiscordMarkdownToMiniMessage converts Discord markdown & emojis into Minecraft MiniMessage tags.

func FormatText

func FormatText(input string) c.Component

FormatText parses MiniMessage, RGB, Legacy color tags, and Click/Hover tags into a Minecraft Component.

func IsVanillaDeathMessage

func IsVanillaDeathMessage(playerName, message string) bool

IsVanillaDeathMessage checks if a chat message looks like a vanilla death announcement.

func RegisterCommands

func RegisterCommands(p *proxy.Proxy, store *ConfigStore, bot *DiscordBot)

RegisterCommands registers /discord and /discord4gate commands in Gate.

func RenderConfig

func RenderConfig(cfg *Config) string

RenderConfig formats the entire configuration with all comments, sections, and values.

func SanitizeForMinecraft

func SanitizeForMinecraft(s string) string

SanitizeForMinecraft converts 4-byte UTF-8 runes (emojis > U+FFFF) to safe shortcodes and filters out invalid control characters to prevent Java Netty DecoderExceptions.

Types

type ChatMessagesSettings

type ChatMessagesSettings struct {
	Message                       string `toml:"message"`
	MessageType                   string `toml:"message_type"`
	MessageEmbedColor             string `toml:"message_embed_color"`
	JoinMessage                   string `toml:"join_message"`
	JoinMessageType               string `toml:"join_message_type"`
	JoinMessageEmbedColor         string `toml:"join_message_embed_color"`
	LeaveMessage                  string `toml:"leave_message"`
	LeaveMessageType              string `toml:"leave_message_type"`
	LeaveMessageEmbedColor        string `toml:"leave_message_embed_color"`
	ServerSwitchMessage           string `toml:"server_switch_message"`
	ServerSwitchMessageType       string `toml:"server_switch_message_type"`
	ServerSwitchMessageEmbedColor string `toml:"server_switch_message_embed_color"`
	ProxyStartMessage             string `toml:"proxy_start_message"`
	ProxyStartMessageType         string `toml:"proxy_start_message_type"`
	ProxyStartMessageEmbedColor   string `toml:"proxy_start_message_embed_color"`
	ProxyStopMessage              string `toml:"proxy_stop_message"`
	ProxyStopMessageType          string `toml:"proxy_stop_message_type"`
	ProxyStopMessageEmbedColor    string `toml:"proxy_stop_message_embed_color"`
	ServerStartMessage            string `toml:"server_start_message"`
	ServerStartMessageType        string `toml:"server_start_message_type"`
	ServerStartMessageEmbedColor  string `toml:"server_start_message_embed_color"`
	ServerStopMessage             string `toml:"server_stop_message"`
	ServerStopMessageType         string `toml:"server_stop_message_type"`
	ServerStopMessageEmbedColor   string `toml:"server_stop_message_embed_color"`
	DeathMessage                  string `toml:"death_message"`
	DeathMessageType              string `toml:"death_message_type"`
	DeathMessageEmbedColor        string `toml:"death_message_embed_color"`
	AdvancementMessage            string `toml:"advancement_message"`
	AdvancementMessageType        string `toml:"advancement_message_type"`
	AdvancementMessageEmbedColor  string `toml:"advancement_message_embed_color"`
}

type CompanionHandler

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

CompanionHandler handles plugin message packets from backend servers (optional companion integration).

func NewCompanionHandler

func NewCompanionHandler(bot *DiscordBot, log logr.Logger) *CompanionHandler

NewCompanionHandler creates a new CompanionHandler instance.

func (*CompanionHandler) HandlePluginMessage

func (h *CompanionHandler) HandlePluginMessage(e *proxy.PluginMessageEvent)

HandlePluginMessage processes incoming messages on discord4gate:main and compatible channels.

type CompanionMessage

type CompanionMessage struct {
	Type        string  `json:"type"`                  // "advancement", "death", "mspt", "chat"
	Server      string  `json:"server,omitempty"`      // server name
	Player      string  `json:"player,omitempty"`      // player username
	Title       string  `json:"title,omitempty"`       // advancement title
	Description string  `json:"description,omitempty"` // advancement description
	Message     string  `json:"message,omitempty"`     // death message or custom chat
	MSPT        float64 `json:"mspt,omitempty"`        // MSPT load
	TPS         float64 `json:"tps,omitempty"`         // TPS
}

CompanionMessage represents the JSON payload from optional backend mods/plugins.

type Config

type Config struct {
	ConfigVersion    int                  `toml:"config_version"`
	ExcludeServers   []string             `toml:"exclude_servers"`
	ExcludedCommands []string             `toml:"excluded_commands"`
	PingInterval     int                  `toml:"ping_interval"`
	DiscordInvite    string               `toml:"discord_invite"`
	Discord          DiscordSettings      `toml:"discord"`
	WebhookRoot      WebhookSettings      `toml:"webhook"`
	ChatRoot         ChatMessagesSettings `toml:"chat"`
}

type ConfigStore

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

func NewConfigStore

func NewConfigStore(path string) *ConfigStore

func (*ConfigStore) Get

func (cs *ConfigStore) Get() *Config

func (*ConfigStore) IsAdmin

func (cs *ConfigStore) IsAdmin(userID string, memberRoles []string) bool

func (*ConfigStore) IsCommandExcluded

func (cs *ConfigStore) IsCommandExcluded(cmd string) bool

func (*ConfigStore) IsServerExcluded

func (cs *ConfigStore) IsServerExcluded(serverName string) bool

func (*ConfigStore) Load

func (cs *ConfigStore) Load() (*Config, error)

func (*ConfigStore) WasUpgraded

func (cs *ConfigStore) WasUpgraded() bool

type DiscordBot

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

DiscordBot manages the connection to Discord, slash commands, chat dispatch, and presence.

func NewDiscordBot

func NewDiscordBot(p *proxy.Proxy, store *ConfigStore, log logr.Logger) *DiscordBot

NewDiscordBot creates a new DiscordBot instance.

func (*DiscordBot) IsShuttingDown

func (b *DiscordBot) IsShuttingDown() bool

func (*DiscordBot) RecordPlayer

func (b *DiscordBot) RecordPlayer(playerID string)

func (*DiscordBot) SendAdvancementMessage

func (b *DiscordBot) SendAdvancementMessage(playerName, title, description string)

SendAdvancementMessage dispatches advancement announcement to Discord.

func (*DiscordBot) SendChatMessage

func (b *DiscordBot) SendChatMessage(player proxy.Player, serverName, message string)

SendChatMessage sends player in-game chat to Discord.

func (*DiscordBot) SendDeathMessage

func (b *DiscordBot) SendDeathMessage(deathMsg string)

SendDeathMessage dispatches death announcement to Discord.

func (*DiscordBot) SendJoinMessage

func (b *DiscordBot) SendJoinMessage(player proxy.Player, serverName string)

SendJoinMessage announces player connection to Discord.

func (*DiscordBot) SendLeaveMessage

func (b *DiscordBot) SendLeaveMessage(player proxy.Player, serverName string)

SendLeaveMessage announces player disconnection to Discord.

func (*DiscordBot) SendProxyStart

func (b *DiscordBot) SendProxyStart()

SendProxyStart sends the proxy start message.

func (*DiscordBot) SendProxyStop

func (b *DiscordBot) SendProxyStop()

SendProxyStop sends the proxy stop message, updates topic to offline, and updates voice channels in parallel.

func (*DiscordBot) SendServerStatusChange

func (b *DiscordBot) SendServerStatusChange(serverName string, isOnline bool)

SendServerStatusChange alerts when a backend server goes offline or boots back up.

func (*DiscordBot) SendServerSwitchMessage

func (b *DiscordBot) SendServerSwitchMessage(player proxy.Player, fromServer, toServer string)

SendServerSwitchMessage announces player server transfer to Discord.

func (*DiscordBot) SetShuttingDown

func (b *DiscordBot) SetShuttingDown(val bool)

func (*DiscordBot) Start

func (b *DiscordBot) Start(ctx context.Context) error

Start connects to Discord and registers commands asynchronously (0s proxy startup lag).

func (*DiscordBot) Stop

func (b *DiscordBot) Stop()

Stop gracefully disconnects the bot synchronously.

type DiscordSettings

type DiscordSettings struct {
	Token                         string               `toml:"token"`
	Channel                       string               `toml:"channel"`
	AdminIDs                      []string             `toml:"admin_ids"`
	MaxPlayers                    int                  `toml:"max_players"`
	NameFormat                    string               `toml:"name_format"`
	EnableHoverTooltips           bool                 `toml:"enable_hover_tooltips"`
	ShowBotMessages               bool                 `toml:"show_bot_messages"`
	ShowAttachmentsIngame         bool                 `toml:"show_attachments_ingame"`
	ShowActivity                  bool                 `toml:"show_activity"`
	ActivityType                  string               `toml:"activity_type"`
	ActivityText                  string               `toml:"activity_text"`
	EnableMentions                bool                 `toml:"enable_mentions"`
	EnableEveryoneAndHere         bool                 `toml:"enable_everyone_and_here"`
	UpdateChannelTopicInterval    int                  `toml:"update_channel_topic_interval"`
	Topic                         string               `toml:"topic"`
	VoicePlayerCountChannelID     string               `toml:"voice_player_count_channel_id"`
	VoicePlayerCountFormat        string               `toml:"voice_player_count_format"`
	VoicePlayerCountOfflineFormat string               `toml:"voice_player_count_offline_format"`
	VoiceStatusChannelID          string               `toml:"voice_status_channel_id"`
	VoiceStatusOnlineFormat       string               `toml:"voice_status_online_format"`
	VoiceStatusOfflineFormat      string               `toml:"voice_status_offline_format"`
	Webhook                       WebhookSettings      `toml:"webhook"`
	Chat                          ChatMessagesSettings `toml:"chat"`
}

type PlayerTracker

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

PlayerTracker maintains a persistent list of unique players who have ever joined.

func NewPlayerTracker

func NewPlayerTracker(path string) *PlayerTracker

NewPlayerTracker creates and initializes a PlayerTracker from disk.

func (*PlayerTracker) Count

func (pt *PlayerTracker) Count() int

Count returns the total number of unique players who have ever joined.

func (*PlayerTracker) Record

func (pt *PlayerTracker) Record(playerID string) bool

Record adds a player identifier and returns true if it was newly recorded.

type ServerMonitor

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

ServerMonitor watches backend server reachability and announces starts/stops to Discord.

func NewServerMonitor

func NewServerMonitor(p *proxy.Proxy, store *ConfigStore, bot *DiscordBot, log logr.Logger) *ServerMonitor

NewServerMonitor creates a new ServerMonitor instance.

func (*ServerMonitor) Start

func (sm *ServerMonitor) Start()

Start begins periodic background server polling independently of init context.

func (*ServerMonitor) Stop

func (sm *ServerMonitor) Stop()

Stop halts the monitor routine.

type WebhookSettings

type WebhookSettings struct {
	WebhookURL      string `toml:"webhook_url"`
	AvatarURL       string `toml:"avatar_url"`
	WebhookUsername string `toml:"webhook_username"`
}

Jump to

Keyboard shortcuts

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