// Package ircserver implements a traditional IRC wire protocol // listener (RFC 1459/2812) that bridges to the neoirc HTTP/JSON // server internals. package ircserver import "strings" // Message represents a parsed IRC wire protocol message. type Message struct { // Prefix is the optional :prefix at the start (may be // empty for client-to-server messages). Prefix string // Command is the IRC command (e.g., "PRIVMSG", "NICK"). Command string // Params holds the positional parameters, including the // trailing parameter (which was preceded by ':' on the // wire). Params []string } // ParseMessage parses a single IRC wire protocol line // (without the trailing CR-LF) into a Message. // Returns nil if the line is empty. // // IRC message format (RFC 1459 §2.3.1): // // [":" prefix SPACE] command { SPACE param } [SPACE ":" trailing] func ParseMessage(line string) *Message { if line == "" { return nil } msg := &Message{} //nolint:exhaustruct // fields set below // Extract prefix if present. if line[0] == ':' { idx := strings.IndexByte(line, ' ') if idx < 0 { // Only a prefix, no command — invalid. return nil } msg.Prefix = line[1:idx] line = line[idx+1:] } // Skip leading spaces. line = strings.TrimLeft(line, " ") if line == "" { return nil } // Extract command. idx := strings.IndexByte(line, ' ') if idx < 0 { msg.Command = strings.ToUpper(line) return msg } msg.Command = strings.ToUpper(line[:idx]) line = line[idx+1:] // Extract parameters. for line != "" { line = strings.TrimLeft(line, " ") if line == "" { break } // Trailing parameter (everything after ':'). if line[0] == ':' { msg.Params = append(msg.Params, line[1:]) break } idx = strings.IndexByte(line, ' ') if idx < 0 { msg.Params = append(msg.Params, line) break } msg.Params = append(msg.Params, line[:idx]) line = line[idx+1:] } return msg } // FormatMessage formats an IRC message into wire protocol // format (without the trailing CR-LF). func FormatMessage( prefix, command string, params ...string, ) string { var buf strings.Builder if prefix != "" { buf.WriteByte(':') buf.WriteString(prefix) buf.WriteByte(' ') } buf.WriteString(command) for i, param := range params { buf.WriteByte(' ') isLast := i == len(params)-1 needsColon := strings.Contains(param, " ") || param == "" || param[0] == ':' if isLast && needsColon { buf.WriteByte(':') } buf.WriteString(param) } return buf.String() }