This is a cross-server chat relay system for linking Velocity proxies with multiple servers, Discord, and Matrix. It syncs chat messages, player movements, and previews images.
- Player Movement Tracking: Announces joins, quits, and server switches
- Image Relay: Share images across servers with Unicode block rendering
- Timezone Support: Automatic Discord timestamp conversion based on player location
- Configurable Relay: Players can toggle relay and image features
- Command Relay: Specific commands like
/meare relayed across servers
/togglerelay [on/off]- Enable/disable seeing relayed chat messages/toggleimage [on/off]- Enable/disable seeing relayed images/globalrelay on/off- Admin command to toggle global relay system/mytimezone set <timezone> <username>- Set your timezone for inbound Discord timestamps
onfim-relays/
├── onfim-lib/ # Core networking and data structures
│ ├── format/ # Event data models
│ ├── out/ # Outbound messaging components
│ └── utils/ # Utilities and configuration
└── onfim-velocity/ # Velocity plugin implementation
├── OnfimVelocity.kt # Main plugin class
├── ChatSender.kt # Chat distribution logic
└── timezone/ # Timezone handling
Onfim uses a distributed peer-to-peer architecture where each Velocity proxy acts as both client and server:
- Port Configuration: Each node binds to port configurable via
SELF_PORT - Dual Protocol: Messages are sent via both UDP (fast, unreliable) and SCTP (reliable, ordered)
- Heartbeat System: Nodes broadcast heartbeats every 30 seconds to discover other nodes
Player Chat → Velocity Event → Serialization → UDP/SCTP Broadcast
↓
Other Nodes → Deserialization → Chat Display
All events extend this base class containing:
type: Event type identifierid: Unique event ID for deduplicationnode: Origin node information
Chat(
plaintext: String, // Processed message text
user: ChatUser, // Sender information
server: EventLocation, // Origin server
platform: String, // "In-Game", "Discord", "Matrix", "Onfim"
context: DiscordContext? // Reply context if applicable
)Join/Quit: Player connection eventsSwitch: Server-to-server transfersSJoin/SQuit: Silent variants (not relayed externally)
Heartbeat: Network topology maintenanceServerMessage: System announcementsImageEvt: Compressed RGB image data
Environment variables required:
ARC_ID: Discord channel ID for main chatMATRIX_MAIN_CHANNEL: Matrix room ID
Player preferences stored in new line delimited lists:
data/no-relay.txt: UUIDs of players with relay disableddata/no-image.txt: UUIDs of players with images disabled
Images are:
- Compressed as gzipped RGB hex strings by
https://github.com/Restitutor/ImgToMC - Transmitted as
ImageEvtwith dimensions - Rendered using Unicode block characters (▏) with color codes
- Displayed line-by-line to players who haven't disabled images
- On player join, timezone is determined via:
- Username-based lookup from external service
- IP geolocation fallback
- Discord timestamps (
<t:timestamp:format>) are automatically converted - Players without timezone data see system time with setup instructions
-
Dependencies:
- See
gradle/libs.versions.toml
- See
-
Building:
./gradlew :onfim-velocity:build
-
Configuration:
- Set required environment variables
- Ensure nodes can reach each other on port 2403
- Add jar to plugins/