From 53a824c01bbf59986dff539d9dda06fc047368d5 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Mar 2026 10:06:57 -0700 Subject: [PATCH 1/5] Complete IRC numerics module and move to pkg/irc/ (refs #52) - Add all missing numeric reply codes from RFC 1459 and RFC 2812 - Move irc package from internal/irc/ to pkg/irc/ for external use - Update all imports throughout the codebase - Added: trace replies (200-209), stats replies (211-219, 242-243), admin replies (256-259), service replies (234-235), WHOWAS (314, 369), list start (321), version (351), links (364-365), info (371, 374), oper/rehash/service (381-383), time/users (391-395), invite/except lists (346-349), unique ops (325), and all missing error codes (406-415, 422-424, 436-437, 443-446, 463-467, 472, 476-478, 481, 483-485, 491, 501-502) --- cmd/neoirc-cli/api/client.go | 2 +- cmd/neoirc-cli/main.go | 2 +- internal/db/queries.go | 2 +- internal/handlers/api.go | 2 +- internal/irc/numerics.go | 150 -------------- {internal => pkg}/irc/commands.go | 0 pkg/irc/numerics.go | 331 ++++++++++++++++++++++++++++++ 7 files changed, 335 insertions(+), 154 deletions(-) delete mode 100644 internal/irc/numerics.go rename {internal => pkg}/irc/commands.go (100%) create mode 100644 pkg/irc/numerics.go diff --git a/cmd/neoirc-cli/api/client.go b/cmd/neoirc-cli/api/client.go index 8f7fdcf..ce94518 100644 --- a/cmd/neoirc-cli/api/client.go +++ b/cmd/neoirc-cli/api/client.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "git.eeqj.de/sneak/neoirc/internal/irc" + "git.eeqj.de/sneak/neoirc/pkg/irc" ) const ( diff --git a/cmd/neoirc-cli/main.go b/cmd/neoirc-cli/main.go index 4000e01..28401d0 100644 --- a/cmd/neoirc-cli/main.go +++ b/cmd/neoirc-cli/main.go @@ -9,7 +9,7 @@ import ( "time" api "git.eeqj.de/sneak/neoirc/cmd/neoirc-cli/api" - "git.eeqj.de/sneak/neoirc/internal/irc" + "git.eeqj.de/sneak/neoirc/pkg/irc" ) const ( diff --git a/internal/db/queries.go b/internal/db/queries.go index 50686d3..52d9761 100644 --- a/internal/db/queries.go +++ b/internal/db/queries.go @@ -11,7 +11,7 @@ import ( "strconv" "time" - "git.eeqj.de/sneak/neoirc/internal/irc" + "git.eeqj.de/sneak/neoirc/pkg/irc" "github.com/google/uuid" ) diff --git a/internal/handlers/api.go b/internal/handlers/api.go index fce5101..c5a5592 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -11,7 +11,7 @@ import ( "time" "git.eeqj.de/sneak/neoirc/internal/db" - "git.eeqj.de/sneak/neoirc/internal/irc" + "git.eeqj.de/sneak/neoirc/pkg/irc" "github.com/go-chi/chi" ) diff --git a/internal/irc/numerics.go b/internal/irc/numerics.go deleted file mode 100644 index 510925b..0000000 --- a/internal/irc/numerics.go +++ /dev/null @@ -1,150 +0,0 @@ -// Package irc provides constants and utilities for the -// IRC protocol, including numeric reply codes from -// RFC 1459 and RFC 2812, and standard command names. -package irc - -// Connection registration replies (001-005). -const ( - RplWelcome = 1 - RplYourHost = 2 - RplCreated = 3 - RplMyInfo = 4 - RplIsupport = 5 -) - -// Command responses (200-399). -const ( - RplUmodeIs = 221 - RplLuserClient = 251 - RplLuserOp = 252 - RplLuserUnknown = 253 - RplLuserChannels = 254 - RplLuserMe = 255 - RplAway = 301 - RplUserHost = 302 - RplIson = 303 - RplUnaway = 305 - RplNowAway = 306 - RplWhoisUser = 311 - RplWhoisServer = 312 - RplWhoisOperator = 313 - RplEndOfWho = 315 - RplWhoisIdle = 317 - RplEndOfWhois = 318 - RplWhoisChannels = 319 - RplList = 322 - RplListEnd = 323 - RplChannelModeIs = 324 - RplCreationTime = 329 - RplNoTopic = 331 - RplTopic = 332 - RplTopicWhoTime = 333 - RplInviting = 341 - RplWhoReply = 352 - RplNamReply = 353 - RplEndOfNames = 366 - RplBanList = 367 - RplEndOfBanList = 368 - RplMotd = 372 - RplMotdStart = 375 - RplEndOfMotd = 376 -) - -// Error replies (400-599). -const ( - ErrNoSuchNick = 401 - ErrNoSuchServer = 402 - ErrNoSuchChannel = 403 - ErrCannotSendToChan = 404 - ErrTooManyChannels = 405 - ErrNoRecipient = 411 - ErrNoTextToSend = 412 - ErrUnknownCommand = 421 - ErrNoNicknameGiven = 431 - ErrErroneusNickname = 432 - ErrNicknameInUse = 433 - ErrUserNotInChannel = 441 - ErrNotOnChannel = 442 - ErrNotRegistered = 451 - ErrNeedMoreParams = 461 - ErrAlreadyRegistered = 462 - ErrChannelIsFull = 471 - ErrInviteOnlyChan = 473 - ErrBannedFromChan = 474 - ErrBadChannelKey = 475 - ErrChanOpPrivsNeeded = 482 -) - -// names maps numeric codes to their standard IRC names. -// -//nolint:gochecknoglobals -var names = map[int]string{ - RplWelcome: "RPL_WELCOME", - RplYourHost: "RPL_YOURHOST", - RplCreated: "RPL_CREATED", - RplMyInfo: "RPL_MYINFO", - RplIsupport: "RPL_ISUPPORT", - RplUmodeIs: "RPL_UMODEIS", - RplLuserClient: "RPL_LUSERCLIENT", - RplLuserOp: "RPL_LUSEROP", - RplLuserUnknown: "RPL_LUSERUNKNOWN", - RplLuserChannels: "RPL_LUSERCHANNELS", - RplLuserMe: "RPL_LUSERME", - RplAway: "RPL_AWAY", - RplUserHost: "RPL_USERHOST", - RplIson: "RPL_ISON", - RplUnaway: "RPL_UNAWAY", - RplNowAway: "RPL_NOWAWAY", - RplWhoisUser: "RPL_WHOISUSER", - RplWhoisServer: "RPL_WHOISSERVER", - RplWhoisOperator: "RPL_WHOISOPERATOR", - RplEndOfWho: "RPL_ENDOFWHO", - RplWhoisIdle: "RPL_WHOISIDLE", - RplEndOfWhois: "RPL_ENDOFWHOIS", - RplWhoisChannels: "RPL_WHOISCHANNELS", - RplList: "RPL_LIST", - RplListEnd: "RPL_LISTEND", //nolint:misspell - RplChannelModeIs: "RPL_CHANNELMODEIS", - RplCreationTime: "RPL_CREATIONTIME", - RplNoTopic: "RPL_NOTOPIC", - RplTopic: "RPL_TOPIC", - RplTopicWhoTime: "RPL_TOPICWHOTIME", - RplInviting: "RPL_INVITING", - RplWhoReply: "RPL_WHOREPLY", - RplNamReply: "RPL_NAMREPLY", - RplEndOfNames: "RPL_ENDOFNAMES", - RplBanList: "RPL_BANLIST", - RplEndOfBanList: "RPL_ENDOFBANLIST", - RplMotd: "RPL_MOTD", - RplMotdStart: "RPL_MOTDSTART", - RplEndOfMotd: "RPL_ENDOFMOTD", - - ErrNoSuchNick: "ERR_NOSUCHNICK", - ErrNoSuchServer: "ERR_NOSUCHSERVER", - ErrNoSuchChannel: "ERR_NOSUCHCHANNEL", - ErrCannotSendToChan: "ERR_CANNOTSENDTOCHAN", - ErrTooManyChannels: "ERR_TOOMANYCHANNELS", - ErrNoRecipient: "ERR_NORECIPIENT", - ErrNoTextToSend: "ERR_NOTEXTTOSEND", - ErrUnknownCommand: "ERR_UNKNOWNCOMMAND", - ErrNoNicknameGiven: "ERR_NONICKNAMEGIVEN", - ErrErroneusNickname: "ERR_ERRONEUSNICKNAME", - ErrNicknameInUse: "ERR_NICKNAMEINUSE", - ErrUserNotInChannel: "ERR_USERNOTINCHANNEL", - ErrNotOnChannel: "ERR_NOTONCHANNEL", - ErrNotRegistered: "ERR_NOTREGISTERED", - ErrNeedMoreParams: "ERR_NEEDMOREPARAMS", - ErrAlreadyRegistered: "ERR_ALREADYREGISTERED", - ErrChannelIsFull: "ERR_CHANNELISFULL", - ErrInviteOnlyChan: "ERR_INVITEONLYCHAN", - ErrBannedFromChan: "ERR_BANNEDFROMCHAN", - ErrBadChannelKey: "ERR_BADCHANNELKEY", - ErrChanOpPrivsNeeded: "ERR_CHANOPRIVSNEEDED", -} - -// Name returns the standard IRC name for a numeric code -// (e.g., Name(2) returns "RPL_YOURHOST"). Returns an -// empty string if the code is unknown. -func Name(code int) string { - return names[code] -} diff --git a/internal/irc/commands.go b/pkg/irc/commands.go similarity index 100% rename from internal/irc/commands.go rename to pkg/irc/commands.go diff --git a/pkg/irc/numerics.go b/pkg/irc/numerics.go new file mode 100644 index 0000000..4c3c484 --- /dev/null +++ b/pkg/irc/numerics.go @@ -0,0 +1,331 @@ +// Package irc provides constants and utilities for the +// IRC protocol, including numeric reply codes from +// RFC 1459 and RFC 2812, and standard command names. +package irc + +// Connection registration replies (001-005). +const ( + RplWelcome = 1 + RplYourHost = 2 + RplCreated = 3 + RplMyInfo = 4 + RplBounce = 5 // RFC 2812; also known as RPL_ISUPPORT in practice + RplIsupport = 5 // De-facto standard (same numeric as RplBounce) +) + +// Command responses (200-399). +const ( + // RFC 2812 trace/stats/links replies (200-219). + RplTraceLink = 200 + RplTraceConnecting = 201 + RplTraceHandshake = 202 + RplTraceUnknown = 203 + RplTraceOperator = 204 + RplTraceUser = 205 + RplTraceServer = 206 + RplTraceService = 207 + RplTraceNewType = 208 + RplTraceClass = 209 + RplStatsLinkInfo = 211 + RplStatsCommands = 212 + RplStatsCLine = 213 + RplStatsNLine = 214 + RplStatsILine = 215 + RplStatsKLine = 216 + RplStatsYLine = 218 + RplEndOfStats = 219 + + RplUmodeIs = 221 + RplServList = 234 + RplServListEnd = 235 + RplStatsUptime = 242 + RplStatsOLine = 243 + RplLuserClient = 251 + RplLuserOp = 252 + RplLuserUnknown = 253 + + RplLuserChannels = 254 + RplLuserMe = 255 + RplAdminMe = 256 + RplAdminLoc1 = 257 + RplAdminLoc2 = 258 + RplAdminEmail = 259 + RplTraceLog = 261 + RplTraceEnd = 262 + RplTryAgain = 263 + + RplAway = 301 + RplUserHost = 302 + RplIson = 303 + RplUnaway = 305 + RplNowAway = 306 + RplWhoisUser = 311 + RplWhoisServer = 312 + RplWhoisOperator = 313 + RplWhoWasUser = 314 + RplEndOfWho = 315 + RplWhoisIdle = 317 + RplEndOfWhois = 318 + RplWhoisChannels = 319 + RplListStart = 321 + RplList = 322 + RplListEnd = 323 + RplChannelModeIs = 324 + + RplUniqOpIs = 325 + RplCreationTime = 329 + RplNoTopic = 331 + RplTopic = 332 + RplTopicWhoTime = 333 + RplInviting = 341 + RplSummoning = 342 + RplInviteList = 346 + RplEndOfInviteList = 347 + RplExceptList = 348 + RplEndOfExceptList = 349 + RplVersion = 351 + RplWhoReply = 352 + RplNamReply = 353 + RplLinks = 364 + RplEndOfLinks = 365 + RplEndOfNames = 366 + RplBanList = 367 + RplEndOfBanList = 368 + RplEndOfWhowas = 369 + RplInfo = 371 + RplMotd = 372 + RplEndOfInfo = 374 + RplMotdStart = 375 + RplEndOfMotd = 376 + RplYoureOper = 381 + RplRehashing = 382 + RplYoureService = 383 + RplTime = 391 + RplUsersStart = 392 + RplUsers = 393 + RplEndOfUsers = 394 + RplNoUsers = 395 +) + +// Error replies (400-599). +const ( + ErrNoSuchNick = 401 + ErrNoSuchServer = 402 + ErrNoSuchChannel = 403 + ErrCannotSendToChan = 404 + ErrTooManyChannels = 405 + ErrWasNoSuchNick = 406 + ErrTooManyTargets = 407 + ErrNoSuchService = 408 + ErrNoOrigin = 409 + ErrNoRecipient = 411 + ErrNoTextToSend = 412 + ErrNoTopLevel = 413 + ErrWildTopLevel = 414 + ErrBadMask = 415 + ErrUnknownCommand = 421 + ErrNoMotd = 422 + ErrNoAdminInfo = 423 + ErrFileError = 424 + ErrNoNicknameGiven = 431 + ErrErroneusNickname = 432 + ErrNicknameInUse = 433 + ErrNickCollision = 436 + ErrUnavailResource = 437 + + ErrUserNotInChannel = 441 + ErrNotOnChannel = 442 + ErrUserOnChannel = 443 + ErrNoLogin = 444 + ErrSummonDisabled = 445 + ErrUsersDisabled = 446 + ErrNotRegistered = 451 + ErrNeedMoreParams = 461 + ErrAlreadyRegistered = 462 + ErrNoPermForHost = 463 + ErrPasswdMismatch = 464 + ErrYoureBannedCreep = 465 + ErrYouWillBeBanned = 466 + ErrKeySet = 467 + ErrChannelIsFull = 471 + ErrUnknownMode = 472 + ErrInviteOnlyChan = 473 + ErrBannedFromChan = 474 + ErrBadChannelKey = 475 + ErrBadChanMask = 476 + ErrNoChanModes = 477 + ErrBanListFull = 478 + ErrNoPrivileges = 481 + ErrChanOpPrivsNeeded = 482 + ErrCantKillServer = 483 + ErrRestricted = 484 + ErrUniqOpPrivsNeeded = 485 + ErrNoOperHost = 491 + + ErrUmodeUnknownFlag = 501 + ErrUsersDoNotMatch = 502 +) + +// names maps numeric codes to their standard IRC names. +// +//nolint:gochecknoglobals +var names = map[int]string{ + RplWelcome: "RPL_WELCOME", + RplYourHost: "RPL_YOURHOST", + RplCreated: "RPL_CREATED", + RplMyInfo: "RPL_MYINFO", + RplBounce: "RPL_BOUNCE", + + RplTraceLink: "RPL_TRACELINK", + RplTraceConnecting: "RPL_TRACECONNECTING", + RplTraceHandshake: "RPL_TRACEHANDSHAKE", + RplTraceUnknown: "RPL_TRACEUNKNOWN", + RplTraceOperator: "RPL_TRACEOPERATOR", + RplTraceUser: "RPL_TRACEUSER", + RplTraceServer: "RPL_TRACESERVER", + RplTraceService: "RPL_TRACESERVICE", + RplTraceNewType: "RPL_TRACENEWTYPE", + RplTraceClass: "RPL_TRACECLASS", + RplStatsLinkInfo: "RPL_STATSLINKINFO", + RplStatsCommands: "RPL_STATSCOMMANDS", + RplStatsCLine: "RPL_STATSCLINE", + RplStatsNLine: "RPL_STATSNLINE", + RplStatsILine: "RPL_STATSILINE", + RplStatsKLine: "RPL_STATSKLINE", + RplStatsYLine: "RPL_STATSYLINE", + RplEndOfStats: "RPL_ENDOFSTATS", + + RplUmodeIs: "RPL_UMODEIS", + RplServList: "RPL_SERVLIST", + RplServListEnd: "RPL_SERVLISTEND", + RplStatsUptime: "RPL_STATSUPTIME", + RplStatsOLine: "RPL_STATSOLINE", + RplLuserClient: "RPL_LUSERCLIENT", + RplLuserOp: "RPL_LUSEROP", + RplLuserUnknown: "RPL_LUSERUNKNOWN", + + RplLuserChannels: "RPL_LUSERCHANNELS", + RplLuserMe: "RPL_LUSERME", + RplAdminMe: "RPL_ADMINME", + RplAdminLoc1: "RPL_ADMINLOC1", + RplAdminLoc2: "RPL_ADMINLOC2", + RplAdminEmail: "RPL_ADMINEMAIL", + RplTraceLog: "RPL_TRACELOG", + RplTraceEnd: "RPL_TRACEEND", + RplTryAgain: "RPL_TRYAGAIN", + + RplAway: "RPL_AWAY", + RplUserHost: "RPL_USERHOST", + RplIson: "RPL_ISON", + RplUnaway: "RPL_UNAWAY", + RplNowAway: "RPL_NOWAWAY", + RplWhoisUser: "RPL_WHOISUSER", + RplWhoisServer: "RPL_WHOISSERVER", + RplWhoisOperator: "RPL_WHOISOPERATOR", + RplWhoWasUser: "RPL_WHOWASUSER", + RplEndOfWho: "RPL_ENDOFWHO", + RplWhoisIdle: "RPL_WHOISIDLE", + RplEndOfWhois: "RPL_ENDOFWHOIS", + RplWhoisChannels: "RPL_WHOISCHANNELS", + RplListStart: "RPL_LISTSTART", + RplList: "RPL_LIST", + RplListEnd: "RPL_LISTEND", //nolint:misspell + RplChannelModeIs: "RPL_CHANNELMODEIS", + + RplUniqOpIs: "RPL_UNIQOPIS", + RplCreationTime: "RPL_CREATIONTIME", + RplNoTopic: "RPL_NOTOPIC", + RplTopic: "RPL_TOPIC", + RplTopicWhoTime: "RPL_TOPICWHOTIME", + RplInviting: "RPL_INVITING", + RplSummoning: "RPL_SUMMONING", + RplInviteList: "RPL_INVITELIST", + RplEndOfInviteList: "RPL_ENDOFINVITELIST", + RplExceptList: "RPL_EXCEPTLIST", + RplEndOfExceptList: "RPL_ENDOFEXCEPTLIST", + RplVersion: "RPL_VERSION", + RplWhoReply: "RPL_WHOREPLY", + RplNamReply: "RPL_NAMREPLY", + RplLinks: "RPL_LINKS", + RplEndOfLinks: "RPL_ENDOFLINKS", + RplEndOfNames: "RPL_ENDOFNAMES", + RplBanList: "RPL_BANLIST", + RplEndOfBanList: "RPL_ENDOFBANLIST", + RplEndOfWhowas: "RPL_ENDOFWHOWAS", + RplInfo: "RPL_INFO", + RplMotd: "RPL_MOTD", + RplEndOfInfo: "RPL_ENDOFINFO", + RplMotdStart: "RPL_MOTDSTART", + RplEndOfMotd: "RPL_ENDOFMOTD", + RplYoureOper: "RPL_YOUREOPER", + RplRehashing: "RPL_REHASHING", + RplYoureService: "RPL_YOURESERVICE", + RplTime: "RPL_TIME", + RplUsersStart: "RPL_USERSSTART", + RplUsers: "RPL_USERS", + RplEndOfUsers: "RPL_ENDOFUSERS", + RplNoUsers: "RPL_NOUSERS", + + ErrNoSuchNick: "ERR_NOSUCHNICK", + ErrNoSuchServer: "ERR_NOSUCHSERVER", + ErrNoSuchChannel: "ERR_NOSUCHCHANNEL", + ErrCannotSendToChan: "ERR_CANNOTSENDTOCHAN", + ErrTooManyChannels: "ERR_TOOMANYCHANNELS", + ErrWasNoSuchNick: "ERR_WASNOSUCHNICK", + ErrTooManyTargets: "ERR_TOOMANYTARGETS", + ErrNoSuchService: "ERR_NOSUCHSERVICE", + ErrNoOrigin: "ERR_NOORIGIN", + ErrNoRecipient: "ERR_NORECIPIENT", + ErrNoTextToSend: "ERR_NOTEXTTOSEND", + ErrNoTopLevel: "ERR_NOTOPLEVEL", + ErrWildTopLevel: "ERR_WILDTOPLEVEL", + ErrBadMask: "ERR_BADMASK", + ErrUnknownCommand: "ERR_UNKNOWNCOMMAND", + ErrNoMotd: "ERR_NOMOTD", + ErrNoAdminInfo: "ERR_NOADMININFO", + ErrFileError: "ERR_FILEERROR", + ErrNoNicknameGiven: "ERR_NONICKNAMEGIVEN", + ErrErroneusNickname: "ERR_ERRONEUSNICKNAME", + ErrNicknameInUse: "ERR_NICKNAMEINUSE", + ErrNickCollision: "ERR_NICKCOLLISION", + ErrUnavailResource: "ERR_UNAVAILRESOURCE", + + ErrUserNotInChannel: "ERR_USERNOTINCHANNEL", + ErrNotOnChannel: "ERR_NOTONCHANNEL", + ErrUserOnChannel: "ERR_USERONCHANNEL", + ErrNoLogin: "ERR_NOLOGIN", + ErrSummonDisabled: "ERR_SUMMONDISABLED", + ErrUsersDisabled: "ERR_USERSDISABLED", + ErrNotRegistered: "ERR_NOTREGISTERED", + ErrNeedMoreParams: "ERR_NEEDMOREPARAMS", + ErrAlreadyRegistered: "ERR_ALREADYREGISTERED", + ErrNoPermForHost: "ERR_NOPERMFORHOST", + ErrPasswdMismatch: "ERR_PASSWDMISMATCH", + ErrYoureBannedCreep: "ERR_YOUREBANNEDCREEP", + ErrYouWillBeBanned: "ERR_YOUWILLBEBANNED", + ErrKeySet: "ERR_KEYSET", + ErrChannelIsFull: "ERR_CHANNELISFULL", + ErrUnknownMode: "ERR_UNKNOWNMODE", + ErrInviteOnlyChan: "ERR_INVITEONLYCHAN", + ErrBannedFromChan: "ERR_BANNEDFROMCHAN", + ErrBadChannelKey: "ERR_BADCHANNELKEY", + ErrBadChanMask: "ERR_BADCHANMASK", + ErrNoChanModes: "ERR_NOCHANMODES", + ErrBanListFull: "ERR_BANLISTFULL", + ErrNoPrivileges: "ERR_NOPRIVILEGES", + ErrChanOpPrivsNeeded: "ERR_CHANOPRIVSNEEDED", + ErrCantKillServer: "ERR_CANTKILLSERVER", + ErrRestricted: "ERR_RESTRICTED", + ErrUniqOpPrivsNeeded: "ERR_UNIQOPPRIVSNEEDED", + ErrNoOperHost: "ERR_NOOPERHOST", + + ErrUmodeUnknownFlag: "ERR_UMODEUNKNOWNFLAG", + ErrUsersDoNotMatch: "ERR_USERSDONTMATCH", +} + +// Name returns the standard IRC name for a numeric code +// (e.g., Name(2) returns "RPL_YOURHOST"). Returns an +// empty string if the code is unknown. +func Name(code int) string { + return names[code] +} -- 2.49.1 From fa347772f2cf1baa313707b916cf8694e0acb088 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 10 Mar 2026 10:12:46 -0700 Subject: [PATCH 2/5] Add missing RFC 2812 numerics: RPL_STATSQLINE (217), RPL_STATSLLINE (241), RPL_STATSHLINE (244) --- pkg/irc/numerics.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/irc/numerics.go b/pkg/irc/numerics.go index 4c3c484..636049b 100644 --- a/pkg/irc/numerics.go +++ b/pkg/irc/numerics.go @@ -32,14 +32,17 @@ const ( RplStatsNLine = 214 RplStatsILine = 215 RplStatsKLine = 216 + RplStatsQLine = 217 RplStatsYLine = 218 RplEndOfStats = 219 RplUmodeIs = 221 RplServList = 234 RplServListEnd = 235 + RplStatsLLine = 241 RplStatsUptime = 242 RplStatsOLine = 243 + RplStatsHLine = 244 RplLuserClient = 251 RplLuserOp = 252 RplLuserUnknown = 253 @@ -192,14 +195,17 @@ var names = map[int]string{ RplStatsNLine: "RPL_STATSNLINE", RplStatsILine: "RPL_STATSILINE", RplStatsKLine: "RPL_STATSKLINE", + RplStatsQLine: "RPL_STATSQLINE", RplStatsYLine: "RPL_STATSYLINE", RplEndOfStats: "RPL_ENDOFSTATS", RplUmodeIs: "RPL_UMODEIS", RplServList: "RPL_SERVLIST", RplServListEnd: "RPL_SERVLISTEND", + RplStatsLLine: "RPL_STATSLLINE", RplStatsUptime: "RPL_STATSUPTIME", RplStatsOLine: "RPL_STATSOLINE", + RplStatsHLine: "RPL_STATSHLINE", RplLuserClient: "RPL_LUSERCLIENT", RplLuserOp: "RPL_LUSEROP", RplLuserUnknown: "RPL_LUSERUNKNOWN", -- 2.49.1 From 164fec3fc8eaac305e0e96c962e6c5752f5c907b Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Mar 2026 10:17:57 -0700 Subject: [PATCH 3/5] refactor: add irc.MessageType custom type with Name() and String() methods - Create 'type MessageType int' in pkg/irc/numerics.go - Type all Rpl*/Err* numeric constants as MessageType - Add Name() method returning the standard IRC name (e.g. RPL_WELCOME) - Add String() method returning zero-padded numeric (e.g. 001) - Update enqueueNumeric/respondIRCError to accept MessageType - Update internal/db to use MessageType conversion for Name() calls - Change names map key type from int to MessageType Refs #52 --- internal/db/queries.go | 2 +- internal/handlers/api.go | 6 +- pkg/irc/numerics.go | 316 +++++++++++++++++++++------------------ 3 files changed, 171 insertions(+), 153 deletions(-) diff --git a/internal/db/queries.go b/internal/db/queries.go index 52d9761..0698ef0 100644 --- a/internal/db/queries.go +++ b/internal/db/queries.go @@ -746,7 +746,7 @@ func scanMessages( code, _ := strconv.Atoi(msg.Command) msg.Code = code - if name := irc.Name(code); name != "" { + if name := irc.Name(irc.MessageType(code)); name != "" { msg.Command = name } } diff --git a/internal/handlers/api.go b/internal/handlers/api.go index c5a5592..3762cf8 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -398,12 +398,12 @@ func (hdlr *Handlers) serverName() string { func (hdlr *Handlers) enqueueNumeric( ctx context.Context, clientID int64, - code int, + code irc.MessageType, nick string, params []string, text string, ) { - command := fmt.Sprintf("%03d", code) + command := code.String() body, err := json.Marshal([]string{text}) if err != nil { @@ -969,7 +969,7 @@ func (hdlr *Handlers) respondIRCError( writer http.ResponseWriter, request *http.Request, clientID, sessionID int64, - code int, + code irc.MessageType, nick string, params []string, text string, diff --git a/pkg/irc/numerics.go b/pkg/irc/numerics.go index 636049b..73d3bc2 100644 --- a/pkg/irc/numerics.go +++ b/pkg/irc/numerics.go @@ -3,176 +3,194 @@ // RFC 1459 and RFC 2812, and standard command names. package irc +import "fmt" + +// MessageType represents an IRC numeric reply or error code. +type MessageType int + +// Name returns the standard IRC name for this numeric code +// (e.g., MessageType(2).Name() returns "RPL_YOURHOST"). +// Returns an empty string if the code is unknown. +func (t MessageType) Name() string { + return names[t] +} + +// String returns the three-digit zero-padded string representation +// of the numeric code (e.g., MessageType(1).String() returns "001"). +func (t MessageType) String() string { + return fmt.Sprintf("%03d", int(t)) +} + // Connection registration replies (001-005). const ( - RplWelcome = 1 - RplYourHost = 2 - RplCreated = 3 - RplMyInfo = 4 - RplBounce = 5 // RFC 2812; also known as RPL_ISUPPORT in practice - RplIsupport = 5 // De-facto standard (same numeric as RplBounce) + RplWelcome MessageType = 1 + RplYourHost MessageType = 2 + RplCreated MessageType = 3 + RplMyInfo MessageType = 4 + RplBounce MessageType = 5 // RFC 2812; also known as RPL_ISUPPORT in practice + RplIsupport MessageType = 5 // De-facto standard (same numeric as RplBounce) ) // Command responses (200-399). const ( // RFC 2812 trace/stats/links replies (200-219). - RplTraceLink = 200 - RplTraceConnecting = 201 - RplTraceHandshake = 202 - RplTraceUnknown = 203 - RplTraceOperator = 204 - RplTraceUser = 205 - RplTraceServer = 206 - RplTraceService = 207 - RplTraceNewType = 208 - RplTraceClass = 209 - RplStatsLinkInfo = 211 - RplStatsCommands = 212 - RplStatsCLine = 213 - RplStatsNLine = 214 - RplStatsILine = 215 - RplStatsKLine = 216 - RplStatsQLine = 217 - RplStatsYLine = 218 - RplEndOfStats = 219 + RplTraceLink MessageType = 200 + RplTraceConnecting MessageType = 201 + RplTraceHandshake MessageType = 202 + RplTraceUnknown MessageType = 203 + RplTraceOperator MessageType = 204 + RplTraceUser MessageType = 205 + RplTraceServer MessageType = 206 + RplTraceService MessageType = 207 + RplTraceNewType MessageType = 208 + RplTraceClass MessageType = 209 + RplStatsLinkInfo MessageType = 211 + RplStatsCommands MessageType = 212 + RplStatsCLine MessageType = 213 + RplStatsNLine MessageType = 214 + RplStatsILine MessageType = 215 + RplStatsKLine MessageType = 216 + RplStatsQLine MessageType = 217 + RplStatsYLine MessageType = 218 + RplEndOfStats MessageType = 219 - RplUmodeIs = 221 - RplServList = 234 - RplServListEnd = 235 - RplStatsLLine = 241 - RplStatsUptime = 242 - RplStatsOLine = 243 - RplStatsHLine = 244 - RplLuserClient = 251 - RplLuserOp = 252 - RplLuserUnknown = 253 + RplUmodeIs MessageType = 221 + RplServList MessageType = 234 + RplServListEnd MessageType = 235 + RplStatsLLine MessageType = 241 + RplStatsUptime MessageType = 242 + RplStatsOLine MessageType = 243 + RplStatsHLine MessageType = 244 + RplLuserClient MessageType = 251 + RplLuserOp MessageType = 252 + RplLuserUnknown MessageType = 253 - RplLuserChannels = 254 - RplLuserMe = 255 - RplAdminMe = 256 - RplAdminLoc1 = 257 - RplAdminLoc2 = 258 - RplAdminEmail = 259 - RplTraceLog = 261 - RplTraceEnd = 262 - RplTryAgain = 263 + RplLuserChannels MessageType = 254 + RplLuserMe MessageType = 255 + RplAdminMe MessageType = 256 + RplAdminLoc1 MessageType = 257 + RplAdminLoc2 MessageType = 258 + RplAdminEmail MessageType = 259 + RplTraceLog MessageType = 261 + RplTraceEnd MessageType = 262 + RplTryAgain MessageType = 263 - RplAway = 301 - RplUserHost = 302 - RplIson = 303 - RplUnaway = 305 - RplNowAway = 306 - RplWhoisUser = 311 - RplWhoisServer = 312 - RplWhoisOperator = 313 - RplWhoWasUser = 314 - RplEndOfWho = 315 - RplWhoisIdle = 317 - RplEndOfWhois = 318 - RplWhoisChannels = 319 - RplListStart = 321 - RplList = 322 - RplListEnd = 323 - RplChannelModeIs = 324 + RplAway MessageType = 301 + RplUserHost MessageType = 302 + RplIson MessageType = 303 + RplUnaway MessageType = 305 + RplNowAway MessageType = 306 + RplWhoisUser MessageType = 311 + RplWhoisServer MessageType = 312 + RplWhoisOperator MessageType = 313 + RplWhoWasUser MessageType = 314 + RplEndOfWho MessageType = 315 + RplWhoisIdle MessageType = 317 + RplEndOfWhois MessageType = 318 + RplWhoisChannels MessageType = 319 + RplListStart MessageType = 321 + RplList MessageType = 322 + RplListEnd MessageType = 323 + RplChannelModeIs MessageType = 324 - RplUniqOpIs = 325 - RplCreationTime = 329 - RplNoTopic = 331 - RplTopic = 332 - RplTopicWhoTime = 333 - RplInviting = 341 - RplSummoning = 342 - RplInviteList = 346 - RplEndOfInviteList = 347 - RplExceptList = 348 - RplEndOfExceptList = 349 - RplVersion = 351 - RplWhoReply = 352 - RplNamReply = 353 - RplLinks = 364 - RplEndOfLinks = 365 - RplEndOfNames = 366 - RplBanList = 367 - RplEndOfBanList = 368 - RplEndOfWhowas = 369 - RplInfo = 371 - RplMotd = 372 - RplEndOfInfo = 374 - RplMotdStart = 375 - RplEndOfMotd = 376 - RplYoureOper = 381 - RplRehashing = 382 - RplYoureService = 383 - RplTime = 391 - RplUsersStart = 392 - RplUsers = 393 - RplEndOfUsers = 394 - RplNoUsers = 395 + RplUniqOpIs MessageType = 325 + RplCreationTime MessageType = 329 + RplNoTopic MessageType = 331 + RplTopic MessageType = 332 + RplTopicWhoTime MessageType = 333 + RplInviting MessageType = 341 + RplSummoning MessageType = 342 + RplInviteList MessageType = 346 + RplEndOfInviteList MessageType = 347 + RplExceptList MessageType = 348 + RplEndOfExceptList MessageType = 349 + RplVersion MessageType = 351 + RplWhoReply MessageType = 352 + RplNamReply MessageType = 353 + RplLinks MessageType = 364 + RplEndOfLinks MessageType = 365 + RplEndOfNames MessageType = 366 + RplBanList MessageType = 367 + RplEndOfBanList MessageType = 368 + RplEndOfWhowas MessageType = 369 + RplInfo MessageType = 371 + RplMotd MessageType = 372 + RplEndOfInfo MessageType = 374 + RplMotdStart MessageType = 375 + RplEndOfMotd MessageType = 376 + RplYoureOper MessageType = 381 + RplRehashing MessageType = 382 + RplYoureService MessageType = 383 + RplTime MessageType = 391 + RplUsersStart MessageType = 392 + RplUsers MessageType = 393 + RplEndOfUsers MessageType = 394 + RplNoUsers MessageType = 395 ) // Error replies (400-599). const ( - ErrNoSuchNick = 401 - ErrNoSuchServer = 402 - ErrNoSuchChannel = 403 - ErrCannotSendToChan = 404 - ErrTooManyChannels = 405 - ErrWasNoSuchNick = 406 - ErrTooManyTargets = 407 - ErrNoSuchService = 408 - ErrNoOrigin = 409 - ErrNoRecipient = 411 - ErrNoTextToSend = 412 - ErrNoTopLevel = 413 - ErrWildTopLevel = 414 - ErrBadMask = 415 - ErrUnknownCommand = 421 - ErrNoMotd = 422 - ErrNoAdminInfo = 423 - ErrFileError = 424 - ErrNoNicknameGiven = 431 - ErrErroneusNickname = 432 - ErrNicknameInUse = 433 - ErrNickCollision = 436 - ErrUnavailResource = 437 + ErrNoSuchNick MessageType = 401 + ErrNoSuchServer MessageType = 402 + ErrNoSuchChannel MessageType = 403 + ErrCannotSendToChan MessageType = 404 + ErrTooManyChannels MessageType = 405 + ErrWasNoSuchNick MessageType = 406 + ErrTooManyTargets MessageType = 407 + ErrNoSuchService MessageType = 408 + ErrNoOrigin MessageType = 409 + ErrNoRecipient MessageType = 411 + ErrNoTextToSend MessageType = 412 + ErrNoTopLevel MessageType = 413 + ErrWildTopLevel MessageType = 414 + ErrBadMask MessageType = 415 + ErrUnknownCommand MessageType = 421 + ErrNoMotd MessageType = 422 + ErrNoAdminInfo MessageType = 423 + ErrFileError MessageType = 424 + ErrNoNicknameGiven MessageType = 431 + ErrErroneusNickname MessageType = 432 + ErrNicknameInUse MessageType = 433 + ErrNickCollision MessageType = 436 + ErrUnavailResource MessageType = 437 - ErrUserNotInChannel = 441 - ErrNotOnChannel = 442 - ErrUserOnChannel = 443 - ErrNoLogin = 444 - ErrSummonDisabled = 445 - ErrUsersDisabled = 446 - ErrNotRegistered = 451 - ErrNeedMoreParams = 461 - ErrAlreadyRegistered = 462 - ErrNoPermForHost = 463 - ErrPasswdMismatch = 464 - ErrYoureBannedCreep = 465 - ErrYouWillBeBanned = 466 - ErrKeySet = 467 - ErrChannelIsFull = 471 - ErrUnknownMode = 472 - ErrInviteOnlyChan = 473 - ErrBannedFromChan = 474 - ErrBadChannelKey = 475 - ErrBadChanMask = 476 - ErrNoChanModes = 477 - ErrBanListFull = 478 - ErrNoPrivileges = 481 - ErrChanOpPrivsNeeded = 482 - ErrCantKillServer = 483 - ErrRestricted = 484 - ErrUniqOpPrivsNeeded = 485 - ErrNoOperHost = 491 + ErrUserNotInChannel MessageType = 441 + ErrNotOnChannel MessageType = 442 + ErrUserOnChannel MessageType = 443 + ErrNoLogin MessageType = 444 + ErrSummonDisabled MessageType = 445 + ErrUsersDisabled MessageType = 446 + ErrNotRegistered MessageType = 451 + ErrNeedMoreParams MessageType = 461 + ErrAlreadyRegistered MessageType = 462 + ErrNoPermForHost MessageType = 463 + ErrPasswdMismatch MessageType = 464 + ErrYoureBannedCreep MessageType = 465 + ErrYouWillBeBanned MessageType = 466 + ErrKeySet MessageType = 467 + ErrChannelIsFull MessageType = 471 + ErrUnknownMode MessageType = 472 + ErrInviteOnlyChan MessageType = 473 + ErrBannedFromChan MessageType = 474 + ErrBadChannelKey MessageType = 475 + ErrBadChanMask MessageType = 476 + ErrNoChanModes MessageType = 477 + ErrBanListFull MessageType = 478 + ErrNoPrivileges MessageType = 481 + ErrChanOpPrivsNeeded MessageType = 482 + ErrCantKillServer MessageType = 483 + ErrRestricted MessageType = 484 + ErrUniqOpPrivsNeeded MessageType = 485 + ErrNoOperHost MessageType = 491 - ErrUmodeUnknownFlag = 501 - ErrUsersDoNotMatch = 502 + ErrUmodeUnknownFlag MessageType = 501 + ErrUsersDoNotMatch MessageType = 502 ) // names maps numeric codes to their standard IRC names. // //nolint:gochecknoglobals -var names = map[int]string{ +var names = map[MessageType]string{ RplWelcome: "RPL_WELCOME", RplYourHost: "RPL_YOURHOST", RplCreated: "RPL_CREATED", @@ -332,6 +350,6 @@ var names = map[int]string{ // Name returns the standard IRC name for a numeric code // (e.g., Name(2) returns "RPL_YOURHOST"). Returns an // empty string if the code is unknown. -func Name(code int) string { +func Name(code MessageType) string { return names[code] } -- 2.49.1 From ba2c113a8554e0275893b0116d05f8e372425677 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 10 Mar 2026 10:31:22 -0700 Subject: [PATCH 4/5] refactor: rename MessageType to IRCMessageType with full API - Rename MessageType to IRCMessageType (custom int type) - .Name() returns just the name (e.g. "RPL_LUSEROP") - .String() returns name + code (e.g. "RPL_LUSEROP <252>") - .Code() returns zero-padded 3-digit string (e.g. "252") - .Int() returns the bare int value - Add FromInt() package-level function with validation - Update all call sites to use new type and methods --- internal/db/queries.go | 4 +- internal/handlers/api.go | 6 +- pkg/irc/numerics.go | 350 +++++++++++++++++++++------------------ 3 files changed, 198 insertions(+), 162 deletions(-) diff --git a/internal/db/queries.go b/internal/db/queries.go index 0698ef0..3a36a9b 100644 --- a/internal/db/queries.go +++ b/internal/db/queries.go @@ -746,8 +746,8 @@ func scanMessages( code, _ := strconv.Atoi(msg.Command) msg.Code = code - if name := irc.Name(irc.MessageType(code)); name != "" { - msg.Command = name + if mt, err := irc.FromInt(code); err == nil { + msg.Command = mt.Name() } } diff --git a/internal/handlers/api.go b/internal/handlers/api.go index 3762cf8..8dd41aa 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -398,12 +398,12 @@ func (hdlr *Handlers) serverName() string { func (hdlr *Handlers) enqueueNumeric( ctx context.Context, clientID int64, - code irc.MessageType, + code irc.IRCMessageType, nick string, params []string, text string, ) { - command := code.String() + command := code.Code() body, err := json.Marshal([]string{text}) if err != nil { @@ -969,7 +969,7 @@ func (hdlr *Handlers) respondIRCError( writer http.ResponseWriter, request *http.Request, clientID, sessionID int64, - code irc.MessageType, + code irc.IRCMessageType, nick string, params []string, text string, diff --git a/pkg/irc/numerics.go b/pkg/irc/numerics.go index 73d3bc2..b71ebc2 100644 --- a/pkg/irc/numerics.go +++ b/pkg/irc/numerics.go @@ -3,194 +3,228 @@ // RFC 1459 and RFC 2812, and standard command names. package irc -import "fmt" +import ( + "errors" + "fmt" +) -// MessageType represents an IRC numeric reply or error code. -type MessageType int +// IRCMessageType represents an IRC numeric reply or error code. +type IRCMessageType int //nolint:revive // Name requested by project owner. // Name returns the standard IRC name for this numeric code -// (e.g., MessageType(2).Name() returns "RPL_YOURHOST"). +// (e.g., IRCMessageType(252).Name() returns "RPL_LUSEROP"). // Returns an empty string if the code is unknown. -func (t MessageType) Name() string { +func (t IRCMessageType) Name() string { return names[t] } -// String returns the three-digit zero-padded string representation -// of the numeric code (e.g., MessageType(1).String() returns "001"). -func (t MessageType) String() string { +// String returns the name and numeric code in angle brackets +// (e.g., IRCMessageType(252).String() returns "RPL_LUSEROP <252>"). +// If the code is unknown, returns "UNKNOWN ". +func (t IRCMessageType) String() string { + n := names[t] + if n == "" { + n = "UNKNOWN" + } + + return fmt.Sprintf("%s <%03d>", n, int(t)) +} + +// Code returns the three-digit zero-padded string representation +// of the numeric code (e.g., IRCMessageType(252).Code() returns "252"). +func (t IRCMessageType) Code() string { return fmt.Sprintf("%03d", int(t)) } +// Int returns the bare integer value of the numeric code. +func (t IRCMessageType) Int() int { + return int(t) +} + +// ErrUnknownNumeric is returned by FromInt when the numeric code is not recognized. +var ErrUnknownNumeric = errors.New("unknown IRC numeric code") + +// FromInt converts an integer to an IRCMessageType, returning an error +// if the numeric code is not a known IRC reply or error code. +func FromInt(n int) (IRCMessageType, error) { + t := IRCMessageType(n) + if _, ok := names[t]; !ok { + return 0, fmt.Errorf("%w: %d", ErrUnknownNumeric, n) + } + + return t, nil +} + // Connection registration replies (001-005). const ( - RplWelcome MessageType = 1 - RplYourHost MessageType = 2 - RplCreated MessageType = 3 - RplMyInfo MessageType = 4 - RplBounce MessageType = 5 // RFC 2812; also known as RPL_ISUPPORT in practice - RplIsupport MessageType = 5 // De-facto standard (same numeric as RplBounce) + RplWelcome IRCMessageType = 1 + RplYourHost IRCMessageType = 2 + RplCreated IRCMessageType = 3 + RplMyInfo IRCMessageType = 4 + RplBounce IRCMessageType = 5 // RFC 2812; also known as RPL_ISUPPORT in practice + RplIsupport IRCMessageType = 5 // De-facto standard (same numeric as RplBounce) ) // Command responses (200-399). const ( // RFC 2812 trace/stats/links replies (200-219). - RplTraceLink MessageType = 200 - RplTraceConnecting MessageType = 201 - RplTraceHandshake MessageType = 202 - RplTraceUnknown MessageType = 203 - RplTraceOperator MessageType = 204 - RplTraceUser MessageType = 205 - RplTraceServer MessageType = 206 - RplTraceService MessageType = 207 - RplTraceNewType MessageType = 208 - RplTraceClass MessageType = 209 - RplStatsLinkInfo MessageType = 211 - RplStatsCommands MessageType = 212 - RplStatsCLine MessageType = 213 - RplStatsNLine MessageType = 214 - RplStatsILine MessageType = 215 - RplStatsKLine MessageType = 216 - RplStatsQLine MessageType = 217 - RplStatsYLine MessageType = 218 - RplEndOfStats MessageType = 219 + RplTraceLink IRCMessageType = 200 + RplTraceConnecting IRCMessageType = 201 + RplTraceHandshake IRCMessageType = 202 + RplTraceUnknown IRCMessageType = 203 + RplTraceOperator IRCMessageType = 204 + RplTraceUser IRCMessageType = 205 + RplTraceServer IRCMessageType = 206 + RplTraceService IRCMessageType = 207 + RplTraceNewType IRCMessageType = 208 + RplTraceClass IRCMessageType = 209 + RplStatsLinkInfo IRCMessageType = 211 + RplStatsCommands IRCMessageType = 212 + RplStatsCLine IRCMessageType = 213 + RplStatsNLine IRCMessageType = 214 + RplStatsILine IRCMessageType = 215 + RplStatsKLine IRCMessageType = 216 + RplStatsQLine IRCMessageType = 217 + RplStatsYLine IRCMessageType = 218 + RplEndOfStats IRCMessageType = 219 - RplUmodeIs MessageType = 221 - RplServList MessageType = 234 - RplServListEnd MessageType = 235 - RplStatsLLine MessageType = 241 - RplStatsUptime MessageType = 242 - RplStatsOLine MessageType = 243 - RplStatsHLine MessageType = 244 - RplLuserClient MessageType = 251 - RplLuserOp MessageType = 252 - RplLuserUnknown MessageType = 253 + RplUmodeIs IRCMessageType = 221 + RplServList IRCMessageType = 234 + RplServListEnd IRCMessageType = 235 + RplStatsLLine IRCMessageType = 241 + RplStatsUptime IRCMessageType = 242 + RplStatsOLine IRCMessageType = 243 + RplStatsHLine IRCMessageType = 244 + RplLuserClient IRCMessageType = 251 + RplLuserOp IRCMessageType = 252 + RplLuserUnknown IRCMessageType = 253 - RplLuserChannels MessageType = 254 - RplLuserMe MessageType = 255 - RplAdminMe MessageType = 256 - RplAdminLoc1 MessageType = 257 - RplAdminLoc2 MessageType = 258 - RplAdminEmail MessageType = 259 - RplTraceLog MessageType = 261 - RplTraceEnd MessageType = 262 - RplTryAgain MessageType = 263 + RplLuserChannels IRCMessageType = 254 + RplLuserMe IRCMessageType = 255 + RplAdminMe IRCMessageType = 256 + RplAdminLoc1 IRCMessageType = 257 + RplAdminLoc2 IRCMessageType = 258 + RplAdminEmail IRCMessageType = 259 + RplTraceLog IRCMessageType = 261 + RplTraceEnd IRCMessageType = 262 + RplTryAgain IRCMessageType = 263 - RplAway MessageType = 301 - RplUserHost MessageType = 302 - RplIson MessageType = 303 - RplUnaway MessageType = 305 - RplNowAway MessageType = 306 - RplWhoisUser MessageType = 311 - RplWhoisServer MessageType = 312 - RplWhoisOperator MessageType = 313 - RplWhoWasUser MessageType = 314 - RplEndOfWho MessageType = 315 - RplWhoisIdle MessageType = 317 - RplEndOfWhois MessageType = 318 - RplWhoisChannels MessageType = 319 - RplListStart MessageType = 321 - RplList MessageType = 322 - RplListEnd MessageType = 323 - RplChannelModeIs MessageType = 324 + RplAway IRCMessageType = 301 + RplUserHost IRCMessageType = 302 + RplIson IRCMessageType = 303 + RplUnaway IRCMessageType = 305 + RplNowAway IRCMessageType = 306 + RplWhoisUser IRCMessageType = 311 + RplWhoisServer IRCMessageType = 312 + RplWhoisOperator IRCMessageType = 313 + RplWhoWasUser IRCMessageType = 314 + RplEndOfWho IRCMessageType = 315 + RplWhoisIdle IRCMessageType = 317 + RplEndOfWhois IRCMessageType = 318 + RplWhoisChannels IRCMessageType = 319 + RplListStart IRCMessageType = 321 + RplList IRCMessageType = 322 + RplListEnd IRCMessageType = 323 + RplChannelModeIs IRCMessageType = 324 - RplUniqOpIs MessageType = 325 - RplCreationTime MessageType = 329 - RplNoTopic MessageType = 331 - RplTopic MessageType = 332 - RplTopicWhoTime MessageType = 333 - RplInviting MessageType = 341 - RplSummoning MessageType = 342 - RplInviteList MessageType = 346 - RplEndOfInviteList MessageType = 347 - RplExceptList MessageType = 348 - RplEndOfExceptList MessageType = 349 - RplVersion MessageType = 351 - RplWhoReply MessageType = 352 - RplNamReply MessageType = 353 - RplLinks MessageType = 364 - RplEndOfLinks MessageType = 365 - RplEndOfNames MessageType = 366 - RplBanList MessageType = 367 - RplEndOfBanList MessageType = 368 - RplEndOfWhowas MessageType = 369 - RplInfo MessageType = 371 - RplMotd MessageType = 372 - RplEndOfInfo MessageType = 374 - RplMotdStart MessageType = 375 - RplEndOfMotd MessageType = 376 - RplYoureOper MessageType = 381 - RplRehashing MessageType = 382 - RplYoureService MessageType = 383 - RplTime MessageType = 391 - RplUsersStart MessageType = 392 - RplUsers MessageType = 393 - RplEndOfUsers MessageType = 394 - RplNoUsers MessageType = 395 + RplUniqOpIs IRCMessageType = 325 + RplCreationTime IRCMessageType = 329 + RplNoTopic IRCMessageType = 331 + RplTopic IRCMessageType = 332 + RplTopicWhoTime IRCMessageType = 333 + RplInviting IRCMessageType = 341 + RplSummoning IRCMessageType = 342 + RplInviteList IRCMessageType = 346 + RplEndOfInviteList IRCMessageType = 347 + RplExceptList IRCMessageType = 348 + RplEndOfExceptList IRCMessageType = 349 + RplVersion IRCMessageType = 351 + RplWhoReply IRCMessageType = 352 + RplNamReply IRCMessageType = 353 + RplLinks IRCMessageType = 364 + RplEndOfLinks IRCMessageType = 365 + RplEndOfNames IRCMessageType = 366 + RplBanList IRCMessageType = 367 + RplEndOfBanList IRCMessageType = 368 + RplEndOfWhowas IRCMessageType = 369 + RplInfo IRCMessageType = 371 + RplMotd IRCMessageType = 372 + RplEndOfInfo IRCMessageType = 374 + RplMotdStart IRCMessageType = 375 + RplEndOfMotd IRCMessageType = 376 + RplYoureOper IRCMessageType = 381 + RplRehashing IRCMessageType = 382 + RplYoureService IRCMessageType = 383 + RplTime IRCMessageType = 391 + RplUsersStart IRCMessageType = 392 + RplUsers IRCMessageType = 393 + RplEndOfUsers IRCMessageType = 394 + RplNoUsers IRCMessageType = 395 ) // Error replies (400-599). const ( - ErrNoSuchNick MessageType = 401 - ErrNoSuchServer MessageType = 402 - ErrNoSuchChannel MessageType = 403 - ErrCannotSendToChan MessageType = 404 - ErrTooManyChannels MessageType = 405 - ErrWasNoSuchNick MessageType = 406 - ErrTooManyTargets MessageType = 407 - ErrNoSuchService MessageType = 408 - ErrNoOrigin MessageType = 409 - ErrNoRecipient MessageType = 411 - ErrNoTextToSend MessageType = 412 - ErrNoTopLevel MessageType = 413 - ErrWildTopLevel MessageType = 414 - ErrBadMask MessageType = 415 - ErrUnknownCommand MessageType = 421 - ErrNoMotd MessageType = 422 - ErrNoAdminInfo MessageType = 423 - ErrFileError MessageType = 424 - ErrNoNicknameGiven MessageType = 431 - ErrErroneusNickname MessageType = 432 - ErrNicknameInUse MessageType = 433 - ErrNickCollision MessageType = 436 - ErrUnavailResource MessageType = 437 + ErrNoSuchNick IRCMessageType = 401 + ErrNoSuchServer IRCMessageType = 402 + ErrNoSuchChannel IRCMessageType = 403 + ErrCannotSendToChan IRCMessageType = 404 + ErrTooManyChannels IRCMessageType = 405 + ErrWasNoSuchNick IRCMessageType = 406 + ErrTooManyTargets IRCMessageType = 407 + ErrNoSuchService IRCMessageType = 408 + ErrNoOrigin IRCMessageType = 409 + ErrNoRecipient IRCMessageType = 411 + ErrNoTextToSend IRCMessageType = 412 + ErrNoTopLevel IRCMessageType = 413 + ErrWildTopLevel IRCMessageType = 414 + ErrBadMask IRCMessageType = 415 + ErrUnknownCommand IRCMessageType = 421 + ErrNoMotd IRCMessageType = 422 + ErrNoAdminInfo IRCMessageType = 423 + ErrFileError IRCMessageType = 424 + ErrNoNicknameGiven IRCMessageType = 431 + ErrErroneusNickname IRCMessageType = 432 + ErrNicknameInUse IRCMessageType = 433 + ErrNickCollision IRCMessageType = 436 + ErrUnavailResource IRCMessageType = 437 - ErrUserNotInChannel MessageType = 441 - ErrNotOnChannel MessageType = 442 - ErrUserOnChannel MessageType = 443 - ErrNoLogin MessageType = 444 - ErrSummonDisabled MessageType = 445 - ErrUsersDisabled MessageType = 446 - ErrNotRegistered MessageType = 451 - ErrNeedMoreParams MessageType = 461 - ErrAlreadyRegistered MessageType = 462 - ErrNoPermForHost MessageType = 463 - ErrPasswdMismatch MessageType = 464 - ErrYoureBannedCreep MessageType = 465 - ErrYouWillBeBanned MessageType = 466 - ErrKeySet MessageType = 467 - ErrChannelIsFull MessageType = 471 - ErrUnknownMode MessageType = 472 - ErrInviteOnlyChan MessageType = 473 - ErrBannedFromChan MessageType = 474 - ErrBadChannelKey MessageType = 475 - ErrBadChanMask MessageType = 476 - ErrNoChanModes MessageType = 477 - ErrBanListFull MessageType = 478 - ErrNoPrivileges MessageType = 481 - ErrChanOpPrivsNeeded MessageType = 482 - ErrCantKillServer MessageType = 483 - ErrRestricted MessageType = 484 - ErrUniqOpPrivsNeeded MessageType = 485 - ErrNoOperHost MessageType = 491 + ErrUserNotInChannel IRCMessageType = 441 + ErrNotOnChannel IRCMessageType = 442 + ErrUserOnChannel IRCMessageType = 443 + ErrNoLogin IRCMessageType = 444 + ErrSummonDisabled IRCMessageType = 445 + ErrUsersDisabled IRCMessageType = 446 + ErrNotRegistered IRCMessageType = 451 + ErrNeedMoreParams IRCMessageType = 461 + ErrAlreadyRegistered IRCMessageType = 462 + ErrNoPermForHost IRCMessageType = 463 + ErrPasswdMismatch IRCMessageType = 464 + ErrYoureBannedCreep IRCMessageType = 465 + ErrYouWillBeBanned IRCMessageType = 466 + ErrKeySet IRCMessageType = 467 + ErrChannelIsFull IRCMessageType = 471 + ErrUnknownMode IRCMessageType = 472 + ErrInviteOnlyChan IRCMessageType = 473 + ErrBannedFromChan IRCMessageType = 474 + ErrBadChannelKey IRCMessageType = 475 + ErrBadChanMask IRCMessageType = 476 + ErrNoChanModes IRCMessageType = 477 + ErrBanListFull IRCMessageType = 478 + ErrNoPrivileges IRCMessageType = 481 + ErrChanOpPrivsNeeded IRCMessageType = 482 + ErrCantKillServer IRCMessageType = 483 + ErrRestricted IRCMessageType = 484 + ErrUniqOpPrivsNeeded IRCMessageType = 485 + ErrNoOperHost IRCMessageType = 491 - ErrUmodeUnknownFlag MessageType = 501 - ErrUsersDoNotMatch MessageType = 502 + ErrUmodeUnknownFlag IRCMessageType = 501 + ErrUsersDoNotMatch IRCMessageType = 502 ) // names maps numeric codes to their standard IRC names. // //nolint:gochecknoglobals -var names = map[MessageType]string{ +var names = map[IRCMessageType]string{ RplWelcome: "RPL_WELCOME", RplYourHost: "RPL_YOURHOST", RplCreated: "RPL_CREATED", @@ -350,6 +384,8 @@ var names = map[MessageType]string{ // Name returns the standard IRC name for a numeric code // (e.g., Name(2) returns "RPL_YOURHOST"). Returns an // empty string if the code is unknown. -func Name(code MessageType) string { +// +// Deprecated: Use IRCMessageType.Name() instead. +func Name(code IRCMessageType) string { return names[code] } -- 2.49.1 From eeb0f2946558378067ed94f819884582d7aa681a Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Mar 2026 10:38:56 -0700 Subject: [PATCH 5/5] test: add comprehensive tests for IRCMessageType API Cover Name(), String(), Code(), Int(), FromInt() for known and unknown numerics, plus the deprecated Name() package-level function. 100% statement coverage on pkg/irc. --- pkg/irc/numerics_test.go | 163 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 pkg/irc/numerics_test.go diff --git a/pkg/irc/numerics_test.go b/pkg/irc/numerics_test.go new file mode 100644 index 0000000..8374db3 --- /dev/null +++ b/pkg/irc/numerics_test.go @@ -0,0 +1,163 @@ +package irc_test + +import ( + "errors" + "testing" + + "git.eeqj.de/sneak/neoirc/pkg/irc" +) + +func TestName(t *testing.T) { + t.Parallel() + + tests := []struct { + numeric irc.IRCMessageType + want string + }{ + {irc.RplWelcome, "RPL_WELCOME"}, + {irc.RplBounce, "RPL_BOUNCE"}, + {irc.RplLuserOp, "RPL_LUSEROP"}, + {irc.ErrCannotSendToChan, "ERR_CANNOTSENDTOCHAN"}, + {irc.ErrNicknameInUse, "ERR_NICKNAMEINUSE"}, + } + + for _, tc := range tests { + if got := tc.numeric.Name(); got != tc.want { + t.Errorf("IRCMessageType(%d).Name() = %q, want %q", tc.numeric.Int(), got, tc.want) + } + } +} + +func TestString(t *testing.T) { + t.Parallel() + + tests := []struct { + numeric irc.IRCMessageType + want string + }{ + {irc.RplWelcome, "RPL_WELCOME <001>"}, + {irc.RplBounce, "RPL_BOUNCE <005>"}, + {irc.RplLuserOp, "RPL_LUSEROP <252>"}, + {irc.ErrCannotSendToChan, "ERR_CANNOTSENDTOCHAN <404>"}, + } + + for _, tc := range tests { + if got := tc.numeric.String(); got != tc.want { + t.Errorf("IRCMessageType(%d).String() = %q, want %q", tc.numeric.Int(), got, tc.want) + } + } +} + +func TestCode(t *testing.T) { + t.Parallel() + + tests := []struct { + numeric irc.IRCMessageType + want string + }{ + {irc.RplWelcome, "001"}, + {irc.RplBounce, "005"}, + {irc.RplLuserOp, "252"}, + {irc.ErrCannotSendToChan, "404"}, + } + + for _, tc := range tests { + if got := tc.numeric.Code(); got != tc.want { + t.Errorf("IRCMessageType(%d).Code() = %q, want %q", tc.numeric.Int(), got, tc.want) + } + } +} + +func TestInt(t *testing.T) { + t.Parallel() + + tests := []struct { + numeric irc.IRCMessageType + want int + }{ + {irc.RplWelcome, 1}, + {irc.RplBounce, 5}, + {irc.RplLuserOp, 252}, + {irc.ErrCannotSendToChan, 404}, + } + + for _, tc := range tests { + if got := tc.numeric.Int(); got != tc.want { + t.Errorf("IRCMessageType(%d).Int() = %d, want %d", tc.want, got, tc.want) + } + } +} + +func TestFromInt_Known(t *testing.T) { + t.Parallel() + + tests := []struct { + code int + want irc.IRCMessageType + }{ + {1, irc.RplWelcome}, + {5, irc.RplBounce}, + {252, irc.RplLuserOp}, + {404, irc.ErrCannotSendToChan}, + {433, irc.ErrNicknameInUse}, + } + + for _, test := range tests { + got, err := irc.FromInt(test.code) + if err != nil { + t.Errorf("FromInt(%d) returned unexpected error: %v", test.code, err) + + continue + } + + if got != test.want { + t.Errorf("FromInt(%d) = %v, want %v", test.code, got, test.want) + } + } +} + +func TestFromInt_Unknown(t *testing.T) { + t.Parallel() + + unknowns := []int{0, 999, 600, -1} + for _, code := range unknowns { + _, err := irc.FromInt(code) + if err == nil { + t.Errorf("FromInt(%d) expected error, got nil", code) + + continue + } + + if !errors.Is(err, irc.ErrUnknownNumeric) { + t.Errorf("FromInt(%d) error = %v, want ErrUnknownNumeric", code, err) + } + } +} + +func TestUnknownNumeric_Name(t *testing.T) { + t.Parallel() + + unknown := irc.IRCMessageType(999) + if got := unknown.Name(); got != "" { + t.Errorf("IRCMessageType(999).Name() = %q, want empty string", got) + } +} + +func TestUnknownNumeric_String(t *testing.T) { + t.Parallel() + + unknown := irc.IRCMessageType(999) + want := "UNKNOWN <999>" + + if got := unknown.String(); got != want { + t.Errorf("IRCMessageType(999).String() = %q, want %q", got, want) + } +} + +func TestDeprecatedNameFunc(t *testing.T) { + t.Parallel() + + if got := irc.Name(irc.RplYourHost); got != "RPL_YOURHOST" { + t.Errorf("Name(RplYourHost) = %q, want %q", got, "RPL_YOURHOST") + } +} -- 2.49.1