fix: address review findings — dynamic version, deduplicate KILL, update README
All checks were successful
check / check (push) Successful in 1m3s

This commit is contained in:
clawbot
2026-04-01 14:39:21 -07:00
parent 17479c4f44
commit 327ff37059
3 changed files with 35 additions and 80 deletions

View File

@@ -2307,8 +2307,8 @@ IRC_LISTEN_ADDR=
| Connection | `NICK`, `USER`, `PASS`, `QUIT`, `PING`/`PONG`, `CAP` |
| Channels | `JOIN`, `PART`, `MODE`, `TOPIC`, `NAMES`, `LIST`, `KICK`, `INVITE` |
| Messaging | `PRIVMSG`, `NOTICE` |
| Info | `WHO`, `WHOIS`, `LUSERS`, `MOTD`, `AWAY` |
| Operator | `OPER` (requires `NEOIRC_OPER_NAME` and `NEOIRC_OPER_PASSWORD`) |
| Info | `WHO`, `WHOIS`, `LUSERS`, `MOTD`, `AWAY`, `USERHOST`, `VERSION`, `ADMIN`, `INFO`, `TIME` |
| Operator | `OPER`, `KILL`, `WALLOPS` (requires `NEOIRC_OPER_NAME` and `NEOIRC_OPER_PASSWORD`) |
### Protocol Details
@@ -2820,6 +2820,10 @@ guess is borne by the server (bcrypt), not the client.
login from additional devices via `POST /api/v1/login`
- [x] **Cookie-based auth** — HttpOnly cookies replace Bearer tokens for
all API authentication
- [x] **Tier 3 utility commands** — USERHOST (302), VERSION (351), ADMIN
(256259), INFO (371/374), TIME (391), KILL (oper-only forced
disconnect), WALLOPS (oper-only broadcast to +w users)
- [x] **User mode +w** — wallops usermode via `MODE nick +w/-w`
### Future (1.0+)

View File

@@ -331,18 +331,12 @@ func (hdlr *Handlers) handleKill(
}
lines := bodyLines()
if len(lines) == 0 {
hdlr.respondIRCError(
writer, request, clientID, sessionID,
irc.ErrNeedMoreParams, nick,
[]string{irc.CmdKill},
"Not enough parameters",
)
return
var targetNick string
if len(lines) > 0 {
targetNick = strings.TrimSpace(lines[0])
}
targetNick := strings.TrimSpace(lines[0])
if targetNick == "" {
hdlr.respondIRCError(
writer, request, clientID, sessionID,
@@ -383,8 +377,11 @@ func (hdlr *Handlers) handleKill(
return
}
hdlr.executeKillUser(
request, targetSID, targetNick, nick, reason,
quitReason := "Killed (" + nick + " (" + reason + "))"
hdlr.svc.BroadcastQuit(
request.Context(), targetSID,
targetNick, quitReason,
)
hdlr.respondJSON(writer, request,
@@ -392,71 +389,6 @@ func (hdlr *Handlers) handleKill(
http.StatusOK)
}
// executeKillUser forcibly disconnects a user: broadcasts
// QUIT to their channels, parts all channels, and deletes
// the session.
func (hdlr *Handlers) executeKillUser(
request *http.Request,
targetSID int64,
targetNick, killerNick, reason string,
) {
ctx := request.Context()
quitMsg := "Killed (" + killerNick + " (" + reason + "))"
quitBody, err := json.Marshal([]string{quitMsg})
if err != nil {
hdlr.log.Error(
"marshal kill quit body", "error", err,
)
return
}
channels, _ := hdlr.params.Database.
GetSessionChannels(ctx, targetSID)
notified := map[int64]bool{}
var dbID int64
if len(channels) > 0 {
dbID, _, _ = hdlr.params.Database.InsertMessage(
ctx, irc.CmdQuit, targetNick, "",
nil, json.RawMessage(quitBody), nil,
)
}
for _, chanInfo := range channels {
memberIDs, _ := hdlr.params.Database.
GetChannelMemberIDs(ctx, chanInfo.ID)
for _, mid := range memberIDs {
if mid != targetSID && !notified[mid] {
notified[mid] = true
_ = hdlr.params.Database.EnqueueToSession(
ctx, mid, dbID,
)
hdlr.broker.Notify(mid)
}
}
_ = hdlr.params.Database.PartChannel(
ctx, chanInfo.ID, targetSID,
)
_ = hdlr.params.Database.DeleteChannelIfEmpty(
ctx, chanInfo.ID,
)
}
_ = hdlr.params.Database.DeleteSession(
ctx, targetSID,
)
}
// handleWallops handles the WALLOPS command.
// Broadcasts a message to all users with +w usermode
// (oper only).

View File

@@ -8,10 +8,29 @@ import (
"strings"
"time"
"sneak.berlin/go/neoirc/internal/globals"
"sneak.berlin/go/neoirc/internal/service"
"sneak.berlin/go/neoirc/pkg/irc"
)
// versionString returns the server version for IRC
// responses, falling back to "neoirc-dev" when globals
// are not set (e.g. during tests).
func versionString() string {
name := globals.Appname
ver := globals.Version
if name == "" {
name = "neoirc"
}
if ver == "" {
ver = "dev"
}
return name + "-" + ver
}
// sendIRCError maps a service.IRCError to an IRC numeric
// reply on the wire.
func (c *Conn) sendIRCError(err error) {
@@ -1384,7 +1403,7 @@ func (c *Conn) handleUserhost(
func (c *Conn) handleVersion(ctx context.Context) {
_ = ctx
version := "neoirc-0.1"
version := versionString()
c.sendNumeric(
irc.RplVersion,
@@ -1426,7 +1445,7 @@ func (c *Conn) handleInfo(ctx context.Context) {
infoLines := []string{
"neoirc — IRC semantics over HTTP",
"Version: neoirc-0.1",
"Version: " + versionString(),
"Written in Go",
}