When the session's nick was changed from another client, such as the
web client over the HTTP API, the IRC listener connection kept the old
nick and sent its messages, joins and later NICK changes under it. The
relay now gives the connection the new nick when it delivers a NICK
change whose old nick is the connection's own: nicks are unique, so its
own session made that change. As the relay goroutine can now change the
nick, the command handlers read it under the connection's lock, through
currentNick, as the relay already did.
Model: opus-5-5
The IRC listener's relay dropped every line sent under the connection's own nick, so a user on the web client and an IRC client at once never saw on IRC what they did from the web client. The relay now drops nothing by nick. Instead the fan-out skips only the sending client's own queue: the IRC listener passes its own client, whose handler already wrote the echo, and the HTTP API skips none. Channel messages and PARTs now reach the sender's other clients, so an HTTP client gets its own PART event; neoirc-cli ignores it, as it already shows its own part. README.md names the commands covered.
Judgement call: a PRIVMSG or NOTICE sent on an IRC connection is not echoed back to it, as in IRC.
Model: opus-5-5
An invite for an IRC listener user arrived as a server NOTICE that did
not name the inviter, so no IRC client treated it as an invite. The
relay now sends it as an INVITE line from the inviter, with the channel
taken from the message body, and sends nothing when the body names no
channel. The IRC listener now queues the INVITE with the channel as its
body, as the HTTP API already did, so an invite from either transport
has the same shape. The README's IRC listener section says how an
invite arrives.
Model: opus-5-5
bcrypt cannot hash a password longer than 72 bytes. PASS over the HTTP
API answered one with a 500, and the IRC listener confirmed it without
storing it.
Both transports now refuse a password outside 8 to 72 bytes with the
reply a short one already got: 461 over the HTTP API, a NOTICE over the
IRC listener. setPassword returns its error, and "Password set" is sent
only when the password was stored. A PASS given before registration, an
empty one included, is checked after the welcome burst and gets the
same NOTICE.
Model: opus-5-5
History was selected by msg_to, so whoever next held a released nick, or
created a deleted channel again under its old name, could read the
earlier holder's messages. Each message row now records the session a
DM or INVITE was sent to (to_session_id) and the channel a channel
message was sent to (to_channel_id), and GET /api/v1/history selects by
the one that applies, using its index. A session's DM history now also
follows it across nick changes. A DM or channel message whose recipient
is deleted before it is stored gets the unknown nick or channel error.
Model: opus-5-5
The nick rule moves from internal/handlers to internal/service as
ValidNick, and the HTTP API and both IRC listener NICK paths call it.
The IRC listener used to cut a nick to 32 bytes and accept anything
else, so a nick with spaces such as "bob PRIVMSG #inj :hi" made the
lines built from it read as another user's PRIVMSG. It now answers a
nick the rule refuses with ERR_ERRONEUSNICKNAME (432), before and
after registration, and keeps the nick as it was.
Model: opus-5-5
README.md rules that nicks are case-sensitive, but the IRC listener's
relay and user MODE on both transports compared nicks ignoring case:
an IRC listener user alice never saw a PRIVMSG, NOTICE, JOIN, PART,
NICK or QUIT from Alice, and MODE Alice from alice was taken as her
own nick. These checks now use ==.
Tests cover alice receiving each of these from Alice, MODE Alice from
alice getting 502 on both transports, and Alice registering while
alice exists. README.md drops the text that described the old
behaviour.
Model: opus-5-5
The IRC listener's JOIN now passes the channel key on, INVITE records
the invite, LIST and WHOIS hide +s channels from non-members with the
queries the HTTP API uses, and PRIVMSG to a +H channel gets 404.
The INVITE checks and the channel hashcash check move from the HTTP
handlers into the service, so both transports run the same code. An IRC
client cannot attach a stamp, so its PRIVMSG to a +H channel always
fails that check. The unfiltered channel list query, now unused, is
removed.
Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
The IRC listener relayed only the first line of a PRIVMSG or NOTICE
body; it now sends one line per body line. A relayed MODE change now
carries its key, limit or mask, each value its own parameter, also in
the echo to the user who made it. CR, LF and NUL are removed from each
parameter and from each whole line sent to an IRC listener connection,
so no stored value can start a line of its own. The channel name check
moved into service.JoinChannel, so the IRC listener's JOIN refuses the
same names with 403.
Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
USERHOST, VERSION, ADMIN, INFO, TIME, KILL and WALLOPS work on the
HTTP API and on the IRC listener. The USERHOST reply, the VERSION,
ADMIN and INFO text, KILL and WALLOPS are each one service function
that both transports call. User mode +w selects who receives WALLOPS.
MODE on your own nick queries or changes your user modes through one
parser on both transports: the whole mode string is checked before
anything is stored, and both flags are stored in one transaction. KILL
closes the victim's IRC connection after sending it KILL and ERROR,
without the operator waiting on it. The sessions table gains
is_wallops in 001_initial.sql, as the pre-1.0 rule in REPO_POLICIES.md
asks.
Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
The shared files are fetched from sneak/prompts dd4027b, with this
repository's own entries after the shared content in .gitignore,
.editorconfig and .dockerignore.
Lint and tests are Dockerfile phases (golangci-lint v2.14.0, Debian Go
1.24.13) that the build stage depends on, and the Makefile targets call
the script/ entrypoints. make fmt also formats Markdown with prettier.
Fixes for the new lint findings: the auth cookie is always Secure, an
IRC connection's relay goroutine stops when the connection closes, and
repeated strings are constants. neoirc-cli treats a plain-HTTP server on
localhost or a loopback address as secure, so local use keeps its session.
Whether the 60-second test cap covers building the test phase is open
on sneak/prompts issue 113.
Model: opus-5-5
Changes the Go module path from `git.eeqj.de/sneak/neoirc` to `sneak.berlin/go/neoirc`.
All occurrences updated:
- `go.mod` module directive
- All Go import paths across 35 `.go` files (107 import statements)
- All JSON schema `$id` URIs across 30 `.json` files in `schema/`
No functional changes — this is a pure rename of the module path.
`docker build .` passes clean (formatting, linting, all tests, binary build).
closes#98
Co-authored-by: clawbot <clawbot@users.noreply.git.eeqj.de>
Reviewed-on: #99
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>