2 Commits

Author SHA1 Message Date
bc8df117ab Merge branch 'main' into feature/irc-numerics-batch2
Some checks failed
check / check (push) Failing after 22s
2026-03-09 23:00:43 +01:00
clawbot
df82c6b302 feat: implement IRC numerics batch 2 — connection registration, channel ops, user queries
All checks were successful
check / check (push) Successful in 2m50s
Add comprehensive IRC numeric reply support:

Connection registration (001-005):
- 002 RPL_YOURHOST, 003 RPL_CREATED, 004 RPL_MYINFO, 005 RPL_ISUPPORT
- All sent automatically during session creation after RPL_WELCOME

Server statistics (251-255):
- RPL_LUSERCLIENT, RPL_LUSEROP, RPL_LUSERCHANNELS, RPL_LUSERME
- Sent during connection registration and via LUSERS command

Channel operations:
- MODE command: query channel modes (324 RPL_CHANNELMODEIS, 329 RPL_CREATIONTIME)
- MODE command: query user modes (221 RPL_UMODEIS)
- NAMES command: query channel member list (353/366)
- LIST command: list all channels (322 RPL_LIST, 323 end of list)

User queries:
- WHOIS command: 311/312/318/319 numerics
- WHO command: 352 RPL_WHOREPLY, 315 RPL_ENDOFWHO

Database additions:
- GetChannelCount, ListAllChannelsWithCounts
- GetChannelCreatedAt, GetSessionCreatedAt

Also adds StartTime to Globals for RPL_CREATED and updates README
with comprehensive documentation of all new commands and numerics.

closes #52
2026-03-09 14:53:49 -07:00

View File

@@ -27,15 +27,7 @@ const (
defaultMaxBodySize = 4096 defaultMaxBodySize = 4096
defaultHistLimit = 50 defaultHistLimit = 50
maxHistLimit = 500 maxHistLimit = 500
cmdJoin = "JOIN"
cmdList = "LIST"
cmdNick = "NICK"
cmdPart = "PART"
cmdPrivmsg = "PRIVMSG" cmdPrivmsg = "PRIVMSG"
cmdQuit = "QUIT"
cmdTopic = "TOPIC"
cmdWho = "WHO"
cmdWhois = "WHOIS"
) )
func (hdlr *Handlers) maxBodySize() int64 { func (hdlr *Handlers) maxBodySize() int64 {
@@ -764,32 +756,32 @@ func (hdlr *Handlers) dispatchCommand(
sessionID, clientID, nick, sessionID, clientID, nick,
command, target, body, bodyLines, command, target, body, bodyLines,
) )
case cmdJoin: case "JOIN":
hdlr.handleJoin( hdlr.handleJoin(
writer, request, writer, request,
sessionID, clientID, nick, target, sessionID, clientID, nick, target,
) )
case cmdPart: case "PART":
hdlr.handlePart( hdlr.handlePart(
writer, request, writer, request,
sessionID, clientID, nick, target, body, sessionID, clientID, nick, target, body,
) )
case cmdNick: case "NICK":
hdlr.handleNick( hdlr.handleNick(
writer, request, writer, request,
sessionID, clientID, nick, bodyLines, sessionID, clientID, nick, bodyLines,
) )
case cmdTopic: case "TOPIC":
hdlr.handleTopic( hdlr.handleTopic(
writer, request, writer, request,
sessionID, clientID, nick, sessionID, clientID, nick,
target, body, bodyLines, target, body, bodyLines,
) )
case cmdQuit: case "QUIT":
hdlr.handleQuit( hdlr.handleQuit(
writer, request, sessionID, nick, body, writer, request, sessionID, nick, body,
) )
case "MOTD", cmdList, cmdWho, cmdWhois, "PING": case "MOTD", "LIST", "WHO", "WHOIS", "PING":
hdlr.dispatchInfoCommand( hdlr.dispatchInfoCommand(
writer, request, writer, request,
sessionID, clientID, nick, sessionID, clientID, nick,
@@ -823,18 +815,18 @@ func (hdlr *Handlers) dispatchQueryCommand(
writer, request, writer, request,
sessionID, clientID, nick, target, sessionID, clientID, nick, target,
) )
case cmdList: case "LIST":
hdlr.handleList( hdlr.handleList(
writer, request, writer, request,
sessionID, clientID, nick, sessionID, clientID, nick,
) )
case cmdWhois: case "WHOIS":
hdlr.handleWhois( hdlr.handleWhois(
writer, request, writer, request,
sessionID, clientID, nick, sessionID, clientID, nick,
target, bodyLines, target, bodyLines,
) )
case cmdWho: case "WHO":
hdlr.handleWho( hdlr.handleWho(
writer, request, writer, request,
sessionID, clientID, nick, target, sessionID, clientID, nick, target,
@@ -1081,7 +1073,7 @@ func (hdlr *Handlers) handleJoin(
if target == "" { if target == "" {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdJoin}, "461", nick, []string{"JOIN"},
"Not enough parameters", "Not enough parameters",
) )
@@ -1152,7 +1144,7 @@ func (hdlr *Handlers) executeJoin(
) )
_ = hdlr.fanOutSilent( _ = hdlr.fanOutSilent(
request, cmdJoin, nick, channel, nil, memberIDs, request, "JOIN", nick, channel, nil, memberIDs,
) )
hdlr.deliverJoinNumerics( hdlr.deliverJoinNumerics(
@@ -1250,7 +1242,7 @@ func (hdlr *Handlers) handlePart(
if target == "" { if target == "" {
hdlr.enqueueNumeric( hdlr.enqueueNumeric(
request.Context(), clientID, request.Context(), clientID,
"461", nick, []string{cmdPart}, "461", nick, []string{"PART"},
"Not enough parameters", "Not enough parameters",
) )
hdlr.broker.Notify(sessionID) hdlr.broker.Notify(sessionID)
@@ -1288,7 +1280,7 @@ func (hdlr *Handlers) handlePart(
) )
_ = hdlr.fanOutSilent( _ = hdlr.fanOutSilent(
request, cmdPart, nick, channel, body, memberIDs, request, "PART", nick, channel, body, memberIDs,
) )
err = hdlr.params.Database.PartChannel( err = hdlr.params.Database.PartChannel(
@@ -1330,7 +1322,7 @@ func (hdlr *Handlers) handleNick(
if len(lines) == 0 { if len(lines) == 0 {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdNick}, "461", nick, []string{"NICK"},
"Not enough parameters", "Not enough parameters",
) )
@@ -1428,7 +1420,7 @@ func (hdlr *Handlers) broadcastNick(
} }
dbID, _, _ := hdlr.params.Database.InsertMessage( dbID, _, _ := hdlr.params.Database.InsertMessage(
request.Context(), cmdNick, oldNick, "", request.Context(), "NICK", oldNick, "",
nil, json.RawMessage(nickBody), nil, nil, json.RawMessage(nickBody), nil,
) )
@@ -1469,7 +1461,7 @@ func (hdlr *Handlers) handleTopic(
if target == "" { if target == "" {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdTopic}, "461", nick, []string{"TOPIC"},
"Not enough parameters", "Not enough parameters",
) )
@@ -1480,7 +1472,7 @@ func (hdlr *Handlers) handleTopic(
if len(lines) == 0 { if len(lines) == 0 {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdTopic}, "461", nick, []string{"TOPIC"},
"Not enough parameters", "Not enough parameters",
) )
@@ -1542,7 +1534,7 @@ func (hdlr *Handlers) executeTopic(
) )
_ = hdlr.fanOutSilent( _ = hdlr.fanOutSilent(
request, cmdTopic, nick, channel, body, memberIDs, request, "TOPIC", nick, channel, body, memberIDs,
) )
hdlr.enqueueNumeric( hdlr.enqueueNumeric(
@@ -1575,16 +1567,16 @@ func (hdlr *Handlers) dispatchInfoCommand(
hdlr.deliverMOTD( hdlr.deliverMOTD(
request, clientID, sessionID, nick, request, clientID, sessionID, nick,
) )
case cmdList: case "LIST":
hdlr.handleListCmd( hdlr.handleListCmd(
request, clientID, sessionID, nick, request, clientID, sessionID, nick,
) )
case cmdWho: case "WHO":
hdlr.handleWhoCmd( hdlr.handleWhoCmd(
request, clientID, sessionID, nick, request, clientID, sessionID, nick,
target, target,
) )
case cmdWhois: case "WHOIS":
hdlr.handleWhoisCmd( hdlr.handleWhoisCmd(
request, clientID, sessionID, nick, request, clientID, sessionID, nick,
target, bodyLines, target, bodyLines,
@@ -1665,7 +1657,7 @@ func (hdlr *Handlers) handleWhoCmd(
if target == "" { if target == "" {
hdlr.enqueueNumeric( hdlr.enqueueNumeric(
ctx, clientID, "461", nick, ctx, clientID, "461", nick,
[]string{cmdWho}, "Not enough parameters", []string{"WHO"}, "Not enough parameters",
) )
hdlr.broker.Notify(sessionID) hdlr.broker.Notify(sessionID)
@@ -1736,7 +1728,7 @@ func (hdlr *Handlers) handleWhoisCmd(
if whoisNick == "" { if whoisNick == "" {
hdlr.enqueueNumeric( hdlr.enqueueNumeric(
ctx, clientID, "461", nick, ctx, clientID, "461", nick,
[]string{cmdWhois}, "Not enough parameters", []string{"WHOIS"}, "Not enough parameters",
) )
hdlr.broker.Notify(sessionID) hdlr.broker.Notify(sessionID)
@@ -1839,7 +1831,7 @@ func (hdlr *Handlers) handleQuit(
if len(channels) > 0 { if len(channels) > 0 {
dbID, _, _ = hdlr.params.Database.InsertMessage( dbID, _, _ = hdlr.params.Database.InsertMessage(
request.Context(), cmdQuit, nick, "", request.Context(), "QUIT", nick, "",
nil, body, nil, nil, body, nil,
) )
} }
@@ -2108,7 +2100,7 @@ func (hdlr *Handlers) handleWhois(
if queryNick == "" { if queryNick == "" {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdWhois}, "461", nick, []string{"WHOIS"},
"Not enough parameters", "Not enough parameters",
) )
@@ -2219,7 +2211,7 @@ func (hdlr *Handlers) handleWho(
if target == "" { if target == "" {
hdlr.respondIRCError( hdlr.respondIRCError(
writer, request, clientID, sessionID, writer, request, clientID, sessionID,
"461", nick, []string{cmdWho}, "461", nick, []string{"WHO"},
"Not enough parameters", "Not enough parameters",
) )
@@ -2507,7 +2499,7 @@ func (hdlr *Handlers) cleanupUser(
if len(channels) > 0 { if len(channels) > 0 {
quitDBID, _, _ = hdlr.params.Database.InsertMessage( quitDBID, _, _ = hdlr.params.Database.InsertMessage(
ctx, cmdQuit, nick, "", ctx, "QUIT", nick, "",
nil, nil, nil, nil, nil, nil,
) )
} }