3.2 KiB
irc
An application used to communicate via IRC networks: a client library plus a terminal client.
Implements RFC 1459/2812 message parsing, IRCv3 message tags, TLS, automatic reconnection with exponential backoff, channel state tracking and CTCP.
Install
go install github.com/zg/irc/cmd/irc@latest
Use
irc -nick yournick -channels '#go,#general'
irc -nick yournick -server irc.example.com:6667 -tls=false
irc -nick yournick -v # log every protocol message
Commands: /join, /part, /msg, /me, /nick, /topic, /names,
/switch, /raw, /quit. Anything not starting with / is sent to the
current channel.
Library
client := irc.NewClient(irc.Config{
Server: "irc.libera.chat:6697",
Nick: "mybot",
TLS: true,
Channels: []string{"#mychannel"},
Reconnect: true,
})
client.Handle(irc.CmdPrivmsg, func(c *irc.Client, m *irc.Message) {
if m.Trailing() == "!ping" {
c.Privmsg(m.Param(0), "pong")
}
})
client.Run(ctx)
A handler registered for "*" sees every message, which is what a logger
wants.
Design notes
A blocked socket read ignores context cancellation. Run looked like it
respected its context — there is a select in the read loop — but
scanner.Scan() blocks on the socket and only returns when a line arrives.
Cancelling therefore did nothing until the server next spoke. The connection
is now closed when the context is done, which is the only way to unblock the
read. A test asserts Run returns within three seconds of cancellation.
Silence is probed, not trusted. A TCP connection can stay open long
after the peer has gone. The client PINGs after a quiet interval and drops
the connection when there has been no traffic for PingTimeout, which then
triggers reconnection.
PING is answered before user handlers run. Failing to reply gets the client disconnected, so it cannot be left to a handler that might block or be absent.
Newlines are stripped from everything sent. Without that, a message body
containing \r\n would inject an arbitrary second command — so
PRIVMSG #c :text\r\nQUIT would really quit. There is a test for exactly
that. Lines are also truncated to the 512-byte protocol limit at a rune
boundary, rather than letting the server cut mid-character.
A malformed line never kills the connection. Servers do emit junk; the read loop skips what it cannot parse and carries on. Tested by feeding the client garbage and checking that the next valid message is still delivered.
Nick collisions retry automatically by appending an underscore, and the nick actually in use is taken from the welcome numeric rather than assumed — the server may not give you what you asked for.
Channel state follows JOIN, NAMES, PART, KICK, QUIT and NICK. A nick
change renames the user in every channel rather than leaving a stale entry,
and mode prefixes (@, +) are stripped from NAMES replies.
Tests
The client is tested against a real mock IRC server over a loopback socket, not a stubbed connection — registration, PING/PONG, state tracking and reconnection only work end to end if the socket handling does.
go test -race ./...