From 6a7989e08d063f84f96bd2f982a1c3688ec1ef6f Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 2 Nov 2026 04:48:18 +0100 Subject: [PATCH] List the missing channel modes in the 004 reply (closes #238) The channel-modes parameter of RPL_MYINFO left out modes that each transport's MODE accepts. The IRC listener now sends Himnostv, adding H, i, o and v. The HTTP API now sends Hbiklmnostv, adding H, b and v. Each transport keeps its own list, because they accept different modes. The tests compare the whole parameter, so a missing or extra mode fails them. The README and the schema notes show the new HTTP API list. Model: opus-5-5 --- README.md | 2 +- internal/handlers/api.go | 2 +- internal/handlers/api_test.go | 12 +++++++----- internal/ircserver/conn.go | 2 +- internal/ircserver/server_test.go | 19 ++++++++++--------- schema/README.md | 2 +- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0474e54..d9cef51 100644 --- a/README.md +++ b/README.md @@ -1236,7 +1236,7 @@ IRC listener sends the usual 3-digit code. Over the HTTP API, 005 arrives named | `001` | RPL_WELCOME | After session creation or login, on MOTD | `{"command":"RPL_WELCOME","code":1,"to":"alice","body":["Welcome to the network, alice"]}` | | `002` | RPL_YOURHOST | After session creation or login, on MOTD | `{"command":"RPL_YOURHOST","code":2,"to":"alice","body":["Your host is neoirc, running version 0.1"]}` | | `003` | RPL_CREATED | After session creation or login, on MOTD | `{"command":"RPL_CREATED","code":3,"to":"alice","body":["This server was created 2026-02-10"]}` | -| `004` | RPL_MYINFO | After session creation or login, on MOTD | `{"command":"RPL_MYINFO","code":4,"to":"alice","params":["neoirc","0.1","ow","ikmnostl"]}` | +| `004` | RPL_MYINFO | After session creation or login, on MOTD | `{"command":"RPL_MYINFO","code":4,"to":"alice","params":["neoirc","0.1","ow","Hbiklmnostv"]}` | | `005` | RPL_ISUPPORT | After session creation or login, on MOTD | `{"command":"RPL_BOUNCE","code":5,"to":"alice","params":["CHANTYPES=#","NICKLEN=32","PREFIX=(ov)@+","CHANMODES=b,k,Hl,imnst","NETWORK=neoirc","CASEMAPPING=ascii"],"body":["are supported by this server"]}` | | `221` | RPL_UMODEIS | In response to user MODE | `{"command":"RPL_UMODEIS","code":221,"to":"alice","body":["+"]}` | | `251` | RPL_LUSERCLIENT | On connect, MOTD or LUSERS | `{"command":"RPL_LUSERCLIENT","code":251,"to":"alice","body":["There are 5 users and 0 invisible on 1 servers"]}` | diff --git a/internal/handlers/api.go b/internal/handlers/api.go index 7c0574c..1ec917b 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -412,7 +412,7 @@ func (hdlr *Handlers) deliverWelcome( // 004 RPL_MYINFO hdlr.enqueueNumeric( ctx, clientID, irc.RplMyInfo, nick, - []string{srvName, version, service.UserModes, "ikmnostl"}, + []string{srvName, version, service.UserModes, "Hbiklmnostv"}, "", ) diff --git a/internal/handlers/api_test.go b/internal/handlers/api_test.go index d72d6de..f3642ca 100644 --- a/internal/handlers/api_test.go +++ b/internal/handlers/api_test.go @@ -6797,9 +6797,10 @@ func TestISUPPORT(t *testing.T) { } } -// TestMyInfoListsUserModes verifies the 004 numeric lists -// the user modes o and w as its third parameter. -func TestMyInfoListsUserModes(t *testing.T) { +// TestMyInfoListsModes verifies the 004 numeric lists the +// user modes o and w as its third parameter, and the HTTP +// API's channel modes Hbiklmnostv as its fourth. +func TestMyInfoListsModes(t *testing.T) { t.Parallel() tserver := newTestServer(t) @@ -6813,9 +6814,10 @@ func TestMyInfoListsUserModes(t *testing.T) { } params := getNumericParams(myInfo) - if len(params) != 4 || params[2] != "ow" { + if len(params) != 4 || params[2] != "ow" || + params[3] != "Hbiklmnostv" { t.Fatalf( - "004 params %v, want four with user modes ow third", + "004 params %v, want four ending ow Hbiklmnostv", params, ) } diff --git a/internal/ircserver/conn.go b/internal/ircserver/conn.go index 83f6cbe..edcc6f7 100644 --- a/internal/ircserver/conn.go +++ b/internal/ircserver/conn.go @@ -605,7 +605,7 @@ func (c *Conn) deliverWelcome() { ) c.sendNumeric( irc.RplMyInfo, - c.serverSfx, "neoirc", service.UserModes, "mnst", + c.serverSfx, "neoirc", service.UserModes, "Himnostv", ) c.sendNumeric( irc.RplIsupport, diff --git a/internal/ircserver/server_test.go b/internal/ircserver/server_test.go index 229b43c..17a142d 100644 --- a/internal/ircserver/server_test.go +++ b/internal/ircserver/server_test.go @@ -10,6 +10,7 @@ import ( "net" "os" "runtime" + "slices" "strings" "sync/atomic" "testing" @@ -486,20 +487,20 @@ func TestWelcomeContainsNick(t *testing.T) { } } -// TestMyInfoListsUserModes checks that RPL_MYINFO lists the -// user modes o and w, with no empty parameter before the -// channel modes. -func TestMyInfoListsUserModes(t *testing.T) { +// TestMyInfoListsModes checks that RPL_MYINFO lists the user +// modes o and w, then the channel modes MODE accepts on the +// IRC listener. It compares the whole line, so a missing or +// extra mode fails it. +func TestMyInfoListsModes(t *testing.T) { t.Parallel() env := newTestEnv(t) lines := env.dial(t).register("carol") - assertContains( - t, lines, - ":test.irc 004 carol test.irc neoirc ow mnst", - "RPL_MYINFO with user modes", - ) + want := ":test.irc 004 carol test.irc neoirc ow Himnostv" + if !slices.Contains(lines, want) { + t.Errorf("no line %q in %v", want, lines) + } } func TestPingPong(t *testing.T) { diff --git a/schema/README.md b/schema/README.md index fce0bd8..e5c9409 100644 --- a/schema/README.md +++ b/schema/README.md @@ -145,7 +145,7 @@ Where a schema file and the HTTP API disagree, the HTTP API is right: - `003.json` describes the server's creation date. The HTTP API sends the date the server last started. - `004.json` has example mode lists. The HTTP API sends `ow` as the user modes - and `ikmnostl` as the channel modes. + and `Hbiklmnostv` as the channel modes. - `322.json` says `LIST` gets one reply per channel. The server leaves out a secret channel, one with mode `+s`, unless the user is on it. - `332.json` says `RPL_TOPIC` answers a `TOPIC` query. The HTTP API has no such