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..3a36a9b 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" ) @@ -746,8 +746,8 @@ func scanMessages( code, _ := strconv.Atoi(msg.Command) msg.Code = code - if name := irc.Name(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 fce5101..8dd41aa 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" ) @@ -398,12 +398,12 @@ func (hdlr *Handlers) serverName() string { func (hdlr *Handlers) enqueueNumeric( ctx context.Context, clientID int64, - code int, + code irc.IRCMessageType, nick string, params []string, text string, ) { - command := fmt.Sprintf("%03d", code) + 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 int, + code irc.IRCMessageType, nick string, params []string, text string, 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..b71ebc2 --- /dev/null +++ b/pkg/irc/numerics.go @@ -0,0 +1,391 @@ +// 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 + +import ( + "errors" + "fmt" +) + +// 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., IRCMessageType(252).Name() returns "RPL_LUSEROP"). +// Returns an empty string if the code is unknown. +func (t IRCMessageType) Name() string { + return names[t] +} + +// 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 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 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 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 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 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 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 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 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 IRCMessageType = 501 + ErrUsersDoNotMatch IRCMessageType = 502 +) + +// names maps numeric codes to their standard IRC names. +// +//nolint:gochecknoglobals +var names = map[IRCMessageType]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", + 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", + + 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. +// +// Deprecated: Use IRCMessageType.Name() instead. +func Name(code IRCMessageType) string { + return names[code] +} 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") + } +}